docs: fix a typo in history file
[bbaumbach/samba-autobuild/.git] / source3 / passdb / machine_account_secrets.c
index 61894d4ee662207d381eee7aff123cd698902210..494059b284971173e2ee4ddd8600b003ca5ec478 100644 (file)
 #include "passdb.h"
 #include "../libcli/auth/libcli_auth.h"
 #include "secrets.h"
-#include "dbwrap.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
 
-/* Urrrg. global.... */
-bool global_machine_password_needs_changing;
+static char *domain_info_keystr(const char *domain);
+
+static char *des_salt_key(const char *realm);
 
 /**
  * Form a key for fetching the domain sid
@@ -53,15 +63,87 @@ static const char *domain_sid_keystr(const char *domain)
        return keystr;
 }
 
-bool secrets_store_domain_sid(const char *domain, const struct dom_sid  *sid)
+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(domain_sid_keystr(domain), sid, sizeof(struct dom_sid ));
+       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);
 
-       /* Force a re-query, in case we modified our domain */
-       if (ret)
-               reset_global_sam_sid();
+       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, in the case where we modified our domain */
+       if (ret) {
+               if (dom_sid_equal(get_global_sam_sid(), sid) == false) {
+                       reset_global_sam_sid();
+               }
+       }
        return ret;
 }
 
@@ -85,28 +167,39 @@ bool secrets_fetch_domain_sid(const char *domain, struct dom_sid  *sid)
        return True;
 }
 
-bool secrets_store_domain_guid(const char *domain, struct GUID *guid)
+bool secrets_store_domain_guid(const char *domain, const struct GUID *guid)
 {
-       fstring key;
+       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);
 
-       slprintf(key, sizeof(key)-1, "%s/%s", SECRETS_DOMAIN_GUID, domain);
-       strupper_m(key);
+       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;
-       fstring key;
+       const char *key;
        size_t size = 0;
        struct GUID new_guid;
 
-       slprintf(key, sizeof(key)-1, "%s/%s", SECRETS_DOMAIN_GUID, domain);
-       strupper_m(key);
+       key = domain_guid_keystr(domain);
        dyn_guid = (struct GUID *)secrets_fetch(key, &size);
 
        if (!dyn_guid) {
-               if (lp_server_role() == ROLE_DOMAIN_PDC) {
+               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;
@@ -216,31 +309,13 @@ static const char *trust_keystr(const char *domain)
        return keystr;
 }
 
-/************************************************************************
- Lock the trust password entry.
-************************************************************************/
-
-void *secrets_get_trust_account_lock(TALLOC_CTX *mem_ctx, const char *domain)
-{
-       struct db_context *db_ctx;
-       if (!secrets_init()) {
-               return NULL;
-       }
-
-       db_ctx = secrets_db_ctx();
-
-       return db_ctx->fetch_locked(
-               db_ctx, mem_ctx, string_term_tdb_data(trust_keystr(domain)));
-}
-
 /************************************************************************
  Routine to get the default secure channel type for trust accounts
 ************************************************************************/
 
 enum netr_SchannelType get_default_sec_channel(void)
 {
-       if (lp_server_role() == ROLE_DOMAIN_BDC ||
-           lp_server_role() == ROLE_DOMAIN_PDC) {
+       if (IS_DC) {
                return SEC_CHAN_BDC;
        } else {
                return SEC_CHAN_WKSTA;
@@ -255,7 +330,7 @@ enum netr_SchannelType get_default_sec_channel(void)
 ************************************************************************/
 
 bool secrets_fetch_trust_account_password_legacy(const char *domain,
-                                                uint8 ret_pwd[16],
+                                                uint8_t ret_pwd[16],
                                                 time_t *pass_last_set_time,
                                                 enum netr_SchannelType *channel)
 {
@@ -270,7 +345,7 @@ bool secrets_fetch_trust_account_password_legacy(const char *domain,
 
        if (size != sizeof(*pass)) {
                DEBUG(0, ("secrets were of incorrect size!\n"));
-               SAFE_FREE(pass);
+               BURN_FREE(pass, size);
                return False;
        }
 
@@ -283,88 +358,70 @@ bool secrets_fetch_trust_account_password_legacy(const char *domain,
                *channel = get_default_sec_channel();
        }
 
-       /* Test if machine password has expired and needs to be changed */
-       if (lp_machine_password_timeout()) {
-               if (pass->mod_time > 0 && time(NULL) > (pass->mod_time +
-                               (time_t)lp_machine_password_timeout())) {
-                       global_machine_password_needs_changing = True;
-               }
-       }
-
-       SAFE_FREE(pass);
+       BURN_FREE(pass, size);
        return True;
 }
 
 /************************************************************************
- Routine to get the trust account password for a domain.
- The user of this function must have locked the trust password file using
- the above secrets_lock_trust_account_password().
+ Routine to delete all information related to the domain joined machine.
 ************************************************************************/
 
-bool secrets_fetch_trust_account_password(const char *domain, uint8 ret_pwd[16],
-                                         time_t *pass_last_set_time,
-                                         enum netr_SchannelType *channel)
+bool secrets_delete_machine_password_ex(const char *domain, const char *realm)
 {
-       char *plaintext;
+       const char *tmpkey = NULL;
+       bool ok;
 
-       plaintext = secrets_fetch_machine_password(domain, pass_last_set_time,
-                                                  channel);
-       if (plaintext) {
-               DEBUG(4,("Using cleartext machine password\n"));
-               E_md4hash(plaintext, ret_pwd);
-               SAFE_FREE(plaintext);
-               return True;
+       tmpkey = domain_info_keystr(domain);
+       ok = secrets_delete(tmpkey);
+       if (!ok) {
+               return false;
        }
 
-       return secrets_fetch_trust_account_password_legacy(domain, ret_pwd,
-                                                          pass_last_set_time,
-                                                          channel);
-}
-
-/************************************************************************
- Routine to delete the old plaintext machine account password if any
-************************************************************************/
-
-static bool secrets_delete_prev_machine_password(const char *domain)
-{
-       char *oldpass = (char *)secrets_fetch(machine_prev_password_keystr(domain), NULL);
-       if (oldpass == NULL) {
-               return true;
+       if (realm != NULL) {
+               tmpkey = des_salt_key(domain);
+               ok = secrets_delete(tmpkey);
+               if (!ok) {
+                       return false;
+               }
        }
-       SAFE_FREE(oldpass);
-       return secrets_delete(machine_prev_password_keystr(domain));
-}
 
-/************************************************************************
- Routine to delete the plaintext machine account password and old
- password if any
-************************************************************************/
+       tmpkey = domain_guid_keystr(domain);
+       ok = secrets_delete(tmpkey);
+       if (!ok) {
+               return false;
+       }
 
-bool secrets_delete_machine_password(const char *domain)
-{
-       if (!secrets_delete_prev_machine_password(domain)) {
+       tmpkey = machine_prev_password_keystr(domain);
+       ok = secrets_delete(tmpkey);
+       if (!ok) {
                return false;
        }
-       return secrets_delete(machine_password_keystr(domain));
-}
 
-/************************************************************************
- Routine to delete the plaintext machine account password, old password,
- sec channel type and last change time from secrets database
-************************************************************************/
+       tmpkey = machine_password_keystr(domain);
+       ok = secrets_delete(tmpkey);
+       if (!ok) {
+               return false;
+       }
 
-bool secrets_delete_machine_password_ex(const char *domain)
-{
-       if (!secrets_delete_prev_machine_password(domain)) {
+       tmpkey = machine_sec_channel_type_keystr(domain);
+       ok = secrets_delete(tmpkey);
+       if (!ok) {
                return false;
        }
-       if (!secrets_delete(machine_password_keystr(domain))) {
+
+       tmpkey = machine_last_change_time_keystr(domain);
+       ok = secrets_delete(tmpkey);
+       if (!ok) {
                return false;
        }
-       if (!secrets_delete(machine_sec_channel_type_keystr(domain))) {
+
+       tmpkey = domain_sid_keystr(domain);
+       ok = secrets_delete(tmpkey);
+       if (!ok) {
                return false;
        }
-       return secrets_delete(machine_last_change_time_keystr(domain));
+
+       return true;
 }
 
 /************************************************************************
@@ -373,58 +430,197 @@ bool secrets_delete_machine_password_ex(const char *domain)
 
 bool secrets_delete_domain_sid(const char *domain)
 {
-       return secrets_delete(domain_sid_keystr(domain));
+       return secrets_delete_entry(domain_sid_keystr(domain));
 }
 
 /************************************************************************
- Routine to store the previous machine password (by storing the current password
- as the old)
+ 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)
 ************************************************************************/
 
-static bool secrets_store_prev_machine_password(const char *domain)
+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)
 {
-       char *oldpass;
        bool ret;
+       uint8_t last_change_time_store[4];
+       TALLOC_CTX *frame = talloc_stackframe();
+       uint8_t sec_channel_bytes[4];
 
-       oldpass = (char *)secrets_fetch(machine_password_keystr(domain), NULL);
-       if (oldpass == NULL) {
+       if (delete_join) {
+               secrets_delete_machine_password_ex(domain, realm);
+               TALLOC_FREE(frame);
                return true;
        }
-       ret = secrets_store(machine_prev_password_keystr(domain), oldpass, strlen(oldpass)+1);
-       SAFE_FREE(oldpass);
+
+       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;
 }
 
 /************************************************************************
- Routine to set the plaintext machine account password for a realm
- the password is assumed to be a null terminated ascii string.
- Before storing
+ Return the standard DES salt key
 ************************************************************************/
 
-bool secrets_store_machine_password(const char *pass, const char *domain,
-                                   enum netr_SchannelType sec_channel)
+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;
-       uint32 last_change_time;
-       uint32 sec_channel_type;
 
-       if (!secrets_store_prev_machine_password(domain)) {
-               return false;
+       key = des_salt_key(lp_realm());
+       if (key == NULL) {
+               DEBUG(0,("kerberos_secrets_store_des_salt: failed to generate key!\n"));
+               return False;
        }
 
-       ret = secrets_store(machine_password_keystr(domain), pass, strlen(pass)+1);
-       if (!ret)
-               return ret;
+       if ( !salt ) {
+               DEBUG(8,("kerberos_secrets_store_des_salt: deleting salt\n"));
+               secrets_delete_entry( key );
+               return True;
+       }
 
-       SIVAL(&last_change_time, 0, time(NULL));
-       ret = secrets_store(machine_last_change_time_keystr(domain), &last_change_time, sizeof(last_change_time));
+       DEBUG(3,("kerberos_secrets_store_des_salt: Storing salt \"%s\"\n", salt));
 
-       SIVAL(&sec_channel_type, 0, sec_channel);
-       ret = secrets_store(machine_sec_channel_type_keystr(domain), &sec_channel_type, sizeof(sec_channel_type));
+       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
@@ -436,6 +632,28 @@ 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.
@@ -449,20 +667,12 @@ char *secrets_fetch_machine_password(const char *domain,
        ret = (char *)secrets_fetch(machine_password_keystr(domain), NULL);
 
        if (pass_last_set_time) {
-               size_t size;
-               uint32 *last_set_time;
-               last_set_time = (unsigned int *)secrets_fetch(machine_last_change_time_keystr(domain), &size);
-               if (last_set_time) {
-                       *pass_last_set_time = IVAL(last_set_time,0);
-                       SAFE_FREE(last_set_time);
-               } else {
-                       *pass_last_set_time = 0;
-               }
+               *pass_last_set_time = secrets_fetch_pass_last_set_time(domain);
        }
 
        if (channel) {
                size_t size;
-               uint32 *channel_type;
+               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);
@@ -474,3 +684,1395 @@ char *secrets_fetch_machine_password(const char *domain,
 
        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 strucure still contains the
+        * same secrets_domain_info1_change as currently stored.
+        *
+        * There's always a gap between secrets_prepare_password_change()
+        * and the callers of secrets_check_password_change().
+        */
+
+       status = secrets_fetch_domain_info(domain, mem_ctx, &stored);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("secrets_fetch_domain_info(%s) failed\n", domain);
+               return status;
+       }
+
+       if (stored->next_change == NULL) {
+               /*
+                * We hit a race..., the administrator
+                * rejoined or something similar happened.
+                */
+               DBG_ERR("stored->next_change == NULL for %s.\n", domain);
+               TALLOC_FREE(stored);
+               return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+       }
+
+       if (stored->password_last_change != cookie->password_last_change) {
+               struct timeval store_tv;
+               struct timeval_buf store_buf;
+               struct timeval cookie_tv;
+               struct timeval_buf cookie_buf;
+
+               nttime_to_timeval(&store_tv, stored->password_last_change);
+               nttime_to_timeval(&cookie_tv, cookie->password_last_change);
+
+               DBG_ERR("password_last_change differs %s != %s for %s.\n",
+                       timeval_str_buf(&store_tv, false, false, &store_buf),
+                       timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+                       domain);
+               TALLOC_FREE(stored);
+               return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+       }
+
+       sn = stored->next_change;
+       cn = cookie->next_change;
+
+       if (sn->change_time != cn->change_time) {
+               struct timeval store_tv;
+               struct timeval_buf store_buf;
+               struct timeval cookie_tv;
+               struct timeval_buf cookie_buf;
+
+               nttime_to_timeval(&store_tv, sn->change_time);
+               nttime_to_timeval(&cookie_tv, cn->change_time);
+
+               DBG_ERR("next change_time differs %s != %s for %s.\n",
+                       timeval_str_buf(&store_tv, false, false, &store_buf),
+                       timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+                       domain);
+               TALLOC_FREE(stored);
+               return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+       }
+
+       if (sn->password->change_time != cn->password->change_time) {
+               struct timeval store_tv;
+               struct timeval_buf store_buf;
+               struct timeval cookie_tv;
+               struct timeval_buf cookie_buf;
+
+               nttime_to_timeval(&store_tv, sn->password->change_time);
+               nttime_to_timeval(&cookie_tv, cn->password->change_time);
+
+               DBG_ERR("next password.change_time differs %s != %s for %s.\n",
+                       timeval_str_buf(&store_tv, false, false, &store_buf),
+                       timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+                       domain);
+               TALLOC_FREE(stored);
+               return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+       }
+
+       cmp = 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;
+}