static int pdb_samba_dsdb_replace_by_sam(struct pdb_samba_dsdb_state *state,
				     bool (*need_update)(const struct samu *,
							 enum pdb_elements),
				     struct ldb_dn *dn,
				     struct samu *sam)
{
	TALLOC_CTX *frame = talloc_stackframe();
	int ret = LDB_SUCCESS;
	const char *pw;
	struct ldb_message *msg;
	struct ldb_request *req;
	uint32_t dsdb_flags = 0;
	/* TODO: All fields :-) */

	msg = ldb_msg_new(frame);
	if (!msg) {
		talloc_free(frame);
		return false;
	}

	msg->dn = dn;

	/* build modify request */
	ret = ldb_build_mod_req(&req, state->ldb, frame, msg, NULL, NULL,
				ldb_op_default_callback,
				NULL);
        if (ret != LDB_SUCCESS) {
		talloc_free(frame);
		return ret;
        }

	/* If we set a plaintext password, the system will
	 * force the pwdLastSet to now() */
	if (need_update(sam, PDB_PASSLASTSET)) {
		dsdb_flags |= DSDB_PASSWORD_BYPASS_LAST_SET;

		ret |= pdb_samba_dsdb_add_time(msg, "pwdLastSet",
					   pdb_get_pass_last_set_time(sam));
	}

	pw = pdb_get_plaintext_passwd(sam);
	if (need_update(sam, PDB_PLAINTEXT_PW)) {
		struct ldb_val pw_utf16;
		if (pw == NULL) {
			talloc_free(frame);
			return LDB_ERR_OPERATIONS_ERROR;
		}

		if (!convert_string_talloc(msg,
					   CH_UNIX, CH_UTF16,
					   pw, strlen(pw),
					   (void *)&pw_utf16.data,
					   &pw_utf16.length)) {
			talloc_free(frame);
			return LDB_ERR_OPERATIONS_ERROR;
		}
		ret |= ldb_msg_add_value(msg, "clearTextPassword", &pw_utf16, NULL);
	} else {
		bool changed_lm_pw = false;
		bool changed_nt_pw = false;
		bool changed_history = false;
		if (need_update(sam, PDB_LMPASSWD)) {
			struct ldb_val val;
			val.data = discard_const_p(uint8_t, pdb_get_lanman_passwd(sam));
			if (!val.data) {
				samdb_msg_add_delete(state->ldb, msg, msg,
						     "dBCSPwd");
			} else {
				val.length = LM_HASH_LEN;
				ret |= ldb_msg_add_value(msg, "dBCSPwd", &val, NULL);
			}
			changed_lm_pw = true;
		}
		if (need_update(sam, PDB_NTPASSWD)) {
			struct ldb_val val;
			val.data = discard_const_p(uint8_t, pdb_get_nt_passwd(sam));
			if (!val.data) {
				samdb_msg_add_delete(state->ldb, msg, msg,
						     "unicodePwd");
			} else {
				val.length = NT_HASH_LEN;
				ret |= ldb_msg_add_value(msg, "unicodePwd", &val, NULL);
			}
			changed_nt_pw = true;
		}

		/* Try to ensure we don't get out of sync */
		if (changed_lm_pw && !changed_nt_pw) {
			samdb_msg_add_delete(state->ldb, msg, msg,
					     "unicodePwd");
		} else if (changed_nt_pw && !changed_lm_pw) {
			samdb_msg_add_delete(state->ldb, msg, msg,
					     "dBCSPwd");
		}
		if (changed_lm_pw || changed_nt_pw) {
			samdb_msg_add_delete(state->ldb, msg, msg,
					     "supplementalCredentials");

		}

		if (need_update(sam, PDB_PWHISTORY)) {
			uint32_t current_hist_len;
			const uint8_t *history = pdb_get_pw_history(sam, &current_hist_len);

			bool invalid_history = false;
			struct samr_Password *history_hashes = talloc_array(talloc_tos(), struct samr_Password,
									    current_hist_len);
			if (!history) {
				invalid_history = true;
			} else {
				unsigned int i;
				/* Parse the history into the correct format */
				for (i = 0; i < current_hist_len; i++) {
					if (!all_zero(&history[i*PW_HISTORY_ENTRY_LEN],
						      16)) {
						/* If the history is in the old format, with a salted hash, then we can't migrate it to AD format */
						invalid_history = true;
						break;
					}
					/* Copy out the 2nd 16 bytes of the 32 byte password history, containing the NT hash */
					memcpy(history_hashes[i].hash,
					       &history[(i*PW_HISTORY_ENTRY_LEN) + PW_HISTORY_SALT_LEN],
					       sizeof(history_hashes[i].hash));
				}
			}
			if (invalid_history) {
				ret |= samdb_msg_add_delete(state->ldb, msg, msg,
						     "ntPwdHistory");

				ret |= samdb_msg_add_delete(state->ldb, msg, msg,
						     "lmPwdHistory");
			} else {
				ret |= samdb_msg_add_hashes(state->ldb, msg, msg,
							    "ntPwdHistory",
							    history_hashes,
							    current_hist_len);
			}
			changed_history = true;
		}
		if (changed_lm_pw || changed_nt_pw || changed_history) {
			/* These attributes can only be modified directly by using a special control */
			dsdb_flags |= DSDB_BYPASS_PASSWORD_HASH;
		}
	}

	/* PDB_USERSID is only allowed on ADD, handled in caller */
	if (need_update(sam, PDB_GROUPSID)) {
		const struct dom_sid *sid = pdb_get_group_sid(sam);
		uint32_t rid;
		NTSTATUS status = dom_sid_split_rid(NULL, sid, NULL, &rid);
		if (!NT_STATUS_IS_OK(status)) {
			talloc_free(frame);
			return LDB_ERR_OPERATIONS_ERROR;
		}
		if (!dom_sid_in_domain(samdb_domain_sid(state->ldb), sid)) {
			talloc_free(frame);
			return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
		}
		ret |= samdb_msg_add_uint(state->ldb, msg, msg, "primaryGroupID", rid);
	}
	if (need_update(sam, PDB_FULLNAME)) {
		ret |= ldb_msg_add_string(msg, "displayName", pdb_get_fullname(sam));
	}

	if (need_update(sam, PDB_SMBHOME)) {
		ret |= ldb_msg_add_string(msg, "homeDirectory",
					  pdb_get_homedir(sam));
	}

	if (need_update(sam, PDB_PROFILE)) {
		ret |= ldb_msg_add_string(msg, "profilePath",
					  pdb_get_profile_path(sam));
	}

	if (need_update(sam, PDB_DRIVE)) {
		ret |= ldb_msg_add_string(msg, "homeDrive",
					  pdb_get_dir_drive(sam));
	}

	if (need_update(sam, PDB_LOGONSCRIPT)) {
		ret |= ldb_msg_add_string(msg, "scriptPath",
					  pdb_get_logon_script(sam));
	}

	if (need_update(sam, PDB_KICKOFFTIME)) {
		ret |= pdb_samba_dsdb_add_time(msg, "accountExpires",
					pdb_get_kickoff_time(sam));
	}

	if (need_update(sam, PDB_LOGONTIME)) {
		ret |= pdb_samba_dsdb_add_time(msg, "lastLogon",
					pdb_get_logon_time(sam));
	}

	if (need_update(sam, PDB_LOGOFFTIME)) {
		ret |= pdb_samba_dsdb_add_time(msg, "lastLogoff",
					pdb_get_logoff_time(sam));
	}

	if (need_update(sam, PDB_USERNAME)) {
		ret |= ldb_msg_add_string(msg, "samAccountName",
					  pdb_get_username(sam));
	}

	if (need_update(sam, PDB_HOURSLEN) || need_update(sam, PDB_HOURS)) {
		struct ldb_val hours = data_blob_const(pdb_get_hours(sam), pdb_get_hours_len(sam));
		ret |= ldb_msg_add_value(msg, "logonHours",
					 &hours, NULL);
	}

	if (need_update(sam, PDB_ACCTCTRL)) {
		ret |= samdb_msg_add_acct_flags(state->ldb, msg, msg,
						"userAccountControl", pdb_get_acct_ctrl(sam));
	}

	if (need_update(sam, PDB_COMMENT)) {
		ret |= ldb_msg_add_string(msg, "comment",
					  pdb_get_comment(sam));
	}

	if (need_update(sam, PDB_ACCTDESC)) {
		ret |= ldb_msg_add_string(msg, "description",
					  pdb_get_acct_desc(sam));
	}

	if (need_update(sam, PDB_WORKSTATIONS)) {
		ret |= ldb_msg_add_string(msg, "userWorkstations",
					  pdb_get_workstations(sam));
	}

	/* This will need work, it is actually a UTF8 'string' with internal NULLs, to handle TS parameters */
	if (need_update(sam, PDB_MUNGEDDIAL)) {
		const char *base64_munged_dial = NULL;

		base64_munged_dial = pdb_get_munged_dial(sam);
		if (base64_munged_dial != NULL && strlen(base64_munged_dial) > 0) {
			struct ldb_val blob;

			blob = base64_decode_data_blob_talloc(msg,
							base64_munged_dial);
			if (blob.data == NULL) {
				DEBUG(0, ("Failed to decode userParameters from "
					  "munged dialback string[%s] for %s\n",
					  base64_munged_dial,
					  ldb_dn_get_linearized(msg->dn)));
				talloc_free(frame);
				return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
			}
			ret |= ldb_msg_add_steal_value(msg, "userParameters",
						       &blob);
		}
	}

	if (need_update(sam, PDB_COUNTRY_CODE)) {
		ret |= ldb_msg_add_fmt(msg, "countryCode",
				       "%i", (int)pdb_get_country_code(sam));
	}

	if (need_update(sam, PDB_CODE_PAGE)) {
		ret |= ldb_msg_add_fmt(msg, "codePage",
				       "%i", (int)pdb_get_code_page(sam));
	}

	/* Not yet handled here or not meaningful for modifies on a Samba_Dsdb backend:
	PDB_BAD_PASSWORD_TIME,
	PDB_CANCHANGETIME, - these are calculated per policy, not stored
	PDB_DOMAIN,
	PDB_NTUSERNAME, - this makes no sense, and never really did
	PDB_LOGONDIVS,
	PDB_USERSID, - Handled in pdb_samba_dsdb_add_sam_account()
	PDB_FIELDS_PRESENT,
	PDB_BAD_PASSWORD_COUNT,
	PDB_LOGON_COUNT,
	PDB_UNKNOWN6,
	PDB_BACKEND_PRIVATE_DATA,

 */
	if (ret != LDB_SUCCESS) {
		talloc_free(frame);
		return LDB_ERR_OPERATIONS_ERROR;
	}

	if (msg->num_elements == 0) {
		talloc_free(frame);
		/* Nothing to do, just return success */
		return LDB_SUCCESS;
	}

	ret = dsdb_replace(state->ldb, msg, dsdb_flags);

	if (ret != LDB_SUCCESS) {
		DEBUG(0,("Failed to modify account record %s to set user attributes: %s\n",
			 ldb_dn_get_linearized(msg->dn),
			 ldb_errstring(state->ldb)));
	}

	talloc_free(frame);
	return ret;
}