/*
   Unix SMB/CIFS implementation.

   module to store/fetch session keys for the schannel client

   Copyright (C) Stefan Metzmacher 2013

   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/>.
*/

#include "includes.h"
#include "system/filesys.h"
#include <tevent.h>
#include "lib/util/tevent_ntstatus.h"
#include "lib/dbwrap/dbwrap.h"
#include "lib/dbwrap/dbwrap_rbt.h"
#include "lib/util/util_tdb.h"
#include "libcli/security/security.h"
#include "../lib/param/param.h"
#include "../libcli/auth/schannel.h"
#include "../librpc/gen_ndr/ndr_schannel.h"
#include "../librpc/gen_ndr/ndr_netlogon_c.h"
#include "../librpc/gen_ndr/ndr_netlogon.h"
#include "../librpc/gen_ndr/server_id.h"
#include "netlogon_creds_cli.h"
#include "source3/include/messages.h"
#include "source3/include/g_lock.h"
#include "libds/common/roles.h"
#include "lib/crypto/md4.h"
#include "auth/credentials/credentials.h"
#include "lib/param/loadparm.h"

struct netlogon_creds_cli_locked_state;

struct netlogon_creds_cli_context {
	struct {
		const char *computer;
		const char *account;
		uint32_t proposed_flags;
		uint32_t required_flags;
		enum netr_SchannelType type;
		enum dcerpc_AuthLevel auth_level;
	} client;

	struct {
		const char *computer;
		const char *netbios_domain;
		const char *dns_domain;
		uint32_t cached_flags;
		bool try_validation6;
		bool try_logon_ex;
		bool try_logon_with;
	} server;

	struct {
		const char *key_name;
		TDB_DATA key_data;
		struct db_context *ctx;
		struct g_lock_ctx *g_ctx;
		struct netlogon_creds_cli_locked_state *locked_state;
		enum netlogon_creds_cli_lck_type lock;
	} db;
};

struct netlogon_creds_cli_locked_state {
	struct netlogon_creds_cli_context *context;
	bool is_glocked;
	struct netlogon_creds_CredentialState *creds;
};

static int netlogon_creds_cli_locked_state_destructor(
		struct netlogon_creds_cli_locked_state *state)
{
	struct netlogon_creds_cli_context *context = state->context;

	if (context == NULL) {
		return 0;
	}

	if (context->db.locked_state == state) {
		context->db.locked_state = NULL;
	}

	if (state->is_glocked) {
		g_lock_unlock(context->db.g_ctx,
			      string_term_tdb_data(context->db.key_name));
	}

	return 0;
}

static NTSTATUS netlogon_creds_cli_context_common(
				const char *client_computer,
				const char *client_account,
				enum netr_SchannelType type,
				enum dcerpc_AuthLevel auth_level,
				uint32_t proposed_flags,
				uint32_t required_flags,
				const char *server_computer,
				const char *server_netbios_domain,
				const char *server_dns_domain,
				TALLOC_CTX *mem_ctx,
				struct netlogon_creds_cli_context **_context)
{
	struct netlogon_creds_cli_context *context = NULL;
	char *_key_name = NULL;
	size_t server_netbios_name_len;
	char *p = NULL;

	*_context = NULL;

	context = talloc_zero(mem_ctx, struct netlogon_creds_cli_context);
	if (context == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	context->client.computer = talloc_strdup(context, client_computer);
	if (context->client.computer == NULL) {
		TALLOC_FREE(context);
		return NT_STATUS_NO_MEMORY;
	}

	context->client.account = talloc_strdup(context, client_account);
	if (context->client.account == NULL) {
		TALLOC_FREE(context);
		return NT_STATUS_NO_MEMORY;
	}

	context->client.proposed_flags = proposed_flags;
	context->client.required_flags = required_flags;
	context->client.type = type;
	context->client.auth_level = auth_level;

	context->server.computer = talloc_strdup(context, server_computer);
	if (context->server.computer == NULL) {
		TALLOC_FREE(context);
		return NT_STATUS_NO_MEMORY;
	}

	context->server.netbios_domain = talloc_strdup(context, server_netbios_domain);
	if (context->server.netbios_domain == NULL) {
		TALLOC_FREE(context);
		return NT_STATUS_NO_MEMORY;
	}

	context->server.dns_domain = talloc_strdup(context, server_dns_domain);
	if (context->server.dns_domain == NULL) {
		TALLOC_FREE(context);
		return NT_STATUS_NO_MEMORY;
	}

	/*
	 * TODO:
	 * Force the callers to provide a unique
	 * value for server_computer and use this directly.
	 *
	 * For now we have to deal with
	 * "HOSTNAME" vs. "hostname.example.com".
	 */

	p = strchr(server_computer, '.');
	if (p != NULL) {
		server_netbios_name_len = p-server_computer;
	} else {
		server_netbios_name_len = strlen(server_computer);
	}

	_key_name = talloc_asprintf(context, "CLI[%s/%s]/SRV[%.*s/%s]",
				    client_computer,
				    client_account,
				    (int)server_netbios_name_len,
				    server_computer,
				    server_netbios_domain);
	if (_key_name == NULL) {
		TALLOC_FREE(context);
		return NT_STATUS_NO_MEMORY;
	}

	context->db.key_name = talloc_strdup_upper(context, _key_name);
	TALLOC_FREE(_key_name);
	if (context->db.key_name == NULL) {
		TALLOC_FREE(context);
		return NT_STATUS_NO_MEMORY;
	}

	context->db.key_data = string_term_tdb_data(context->db.key_name);

	*_context = context;
	return NT_STATUS_OK;
}

static struct db_context *netlogon_creds_cli_global_db;

NTSTATUS netlogon_creds_cli_set_global_db(struct loadparm_context *lp_ctx,
					  struct db_context **db)
{
	netlogon_creds_cli_warn_options(lp_ctx);

	if (netlogon_creds_cli_global_db != NULL) {
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	netlogon_creds_cli_global_db = talloc_move(NULL, db);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_open_global_db(struct loadparm_context *lp_ctx)
{
	char *fname;
	struct db_context *global_db;
	int hash_size, tdb_flags;

	netlogon_creds_cli_warn_options(lp_ctx);

	if (netlogon_creds_cli_global_db != NULL) {
		return NT_STATUS_OK;
	}

	fname = lpcfg_private_db_path(NULL, lp_ctx, "netlogon_creds_cli");
	if (fname == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	hash_size = lpcfg_tdb_hash_size(lp_ctx, fname);
	tdb_flags = lpcfg_tdb_flags(
		lp_ctx,
		TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH);

	global_db = dbwrap_local_open(
		NULL,
		fname,
		hash_size,
		tdb_flags,
		O_RDWR|O_CREAT,
		0600,
		DBWRAP_LOCK_ORDER_2,
		DBWRAP_FLAG_NONE);
	if (global_db == NULL) {
		DEBUG(0,("netlogon_creds_cli_open_global_db: Failed to open %s - %s\n",
			 fname, strerror(errno)));
		talloc_free(fname);
		return NT_STATUS_NO_MEMORY;
	}
	TALLOC_FREE(fname);

	netlogon_creds_cli_global_db = global_db;
	return NT_STATUS_OK;
}

void netlogon_creds_cli_close_global_db(void)
{
	TALLOC_FREE(netlogon_creds_cli_global_db);
}

void netlogon_creds_cli_warn_options(struct loadparm_context *lp_ctx)
{
	bool global_reject_md5_servers = lpcfg_reject_md5_servers(lp_ctx);
	bool global_require_strong_key = lpcfg_require_strong_key(lp_ctx);
	int global_client_schannel = lpcfg_client_schannel(lp_ctx);
	bool global_seal_secure_channel = lpcfg_winbind_sealed_pipes(lp_ctx);
	int global_kerberos_enctypes = lpcfg_kerberos_encryption_types(lp_ctx);
	static bool warned_global_reject_md5_servers = false;
	static bool warned_global_require_strong_key = false;
	static bool warned_global_client_schannel = false;
	static bool warned_global_seal_secure_channel = false;
	static bool warned_global_kerberos_encryption_types = false;
	static int warned_global_pid = 0;
	int current_pid = tevent_cached_getpid();

	if (warned_global_pid != current_pid) {
		warned_global_reject_md5_servers = false;
		warned_global_require_strong_key = false;
		warned_global_client_schannel = false;
		warned_global_seal_secure_channel = false;
		warned_global_kerberos_encryption_types = false;
		warned_global_pid = current_pid;
	}

	if (!global_reject_md5_servers && !warned_global_reject_md5_servers) {
		/*
		 * We want admins to notice their misconfiguration!
		 */
		DBG_ERR("CVE-2022-38023 (and others): "
			"Please configure 'reject md5 servers = yes' (the default), "
			"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
		warned_global_reject_md5_servers = true;
	}

	if (!global_require_strong_key && !warned_global_require_strong_key) {
		/*
		 * We want admins to notice their misconfiguration!
		 */
		DBG_ERR("CVE-2022-38023 (and others): "
			"Please configure 'require strong key = yes' (the default), "
			"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
		warned_global_require_strong_key = true;
	}

	if (global_client_schannel != true && !warned_global_client_schannel) {
		/*
		 * We want admins to notice their misconfiguration!
		 */
		DBG_ERR("CVE-2022-38023 (and others): "
			"Please configure 'client schannel = yes' (the default), "
			"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
		warned_global_client_schannel = true;
	}

	if (!global_seal_secure_channel && !warned_global_seal_secure_channel) {
		/*
		 * We want admins to notice their misconfiguration!
		 */
		DBG_ERR("CVE-2022-38023 (and others): "
			"Please configure 'winbind sealed pipes = yes' (the default), "
			"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
		warned_global_seal_secure_channel = true;
	}

	if (global_kerberos_enctypes == KERBEROS_ETYPES_LEGACY &&
	    !warned_global_kerberos_encryption_types)
	{
		/*
		 * We want admins to notice their misconfiguration!
		 */
		DBG_ERR("CVE-2022-37966: "
			"Please void 'kerberos encryption types = legacy', "
			"See https://bugzilla.samba.org/show_bug.cgi?id=15237\n");
		warned_global_kerberos_encryption_types = true;
	}
}

NTSTATUS netlogon_creds_cli_context_global(struct loadparm_context *lp_ctx,
				struct messaging_context *msg_ctx,
				const char *client_account,
				enum netr_SchannelType type,
				const char *server_computer,
				const char *server_netbios_domain,
				const char *server_dns_domain,
				TALLOC_CTX *mem_ctx,
				struct netlogon_creds_cli_context **_context)
{
	TALLOC_CTX *frame = talloc_stackframe();
	NTSTATUS status;
	struct netlogon_creds_cli_context *context = NULL;
	const char *client_computer;
	uint32_t proposed_flags;
	uint32_t required_flags = 0;
	bool reject_md5_servers = true;
	bool require_strong_key = true;
	int require_sign_or_seal = true;
	bool seal_secure_channel = true;
	enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE;
	bool neutralize_nt4_emulation = false;

	*_context = NULL;

	if (msg_ctx == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	client_computer = lpcfg_netbios_name(lp_ctx);
	if (strlen(client_computer) > 15) {
		TALLOC_FREE(frame);
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	/*
	 * allow overwrite per domain
	 * reject md5 servers:<netbios_domain>
	 */
	reject_md5_servers = lpcfg_reject_md5_servers(lp_ctx);
	reject_md5_servers = lpcfg_parm_bool(lp_ctx, NULL,
					     "reject md5 servers",
					     server_netbios_domain,
					     reject_md5_servers);

	/*
	 * allow overwrite per domain
	 * require strong key:<netbios_domain>
	 */
	require_strong_key = lpcfg_require_strong_key(lp_ctx);
	require_strong_key = lpcfg_parm_bool(lp_ctx, NULL,
					     "require strong key",
					     server_netbios_domain,
					     require_strong_key);

	/*
	 * allow overwrite per domain
	 * client schannel:<netbios_domain>
	 */
	require_sign_or_seal = lpcfg_client_schannel(lp_ctx);
	require_sign_or_seal = lpcfg_parm_int(lp_ctx, NULL,
					      "client schannel",
					      server_netbios_domain,
					      require_sign_or_seal);

	/*
	 * allow overwrite per domain
	 * winbind sealed pipes:<netbios_domain>
	 */
	seal_secure_channel = lpcfg_winbind_sealed_pipes(lp_ctx);
	seal_secure_channel = lpcfg_parm_bool(lp_ctx, NULL,
					      "winbind sealed pipes",
					      server_netbios_domain,
					      seal_secure_channel);

	/*
	 * allow overwrite per domain
	 * neutralize nt4 emulation:<netbios_domain>
	 */
	neutralize_nt4_emulation = lpcfg_neutralize_nt4_emulation(lp_ctx);
	neutralize_nt4_emulation = lpcfg_parm_bool(lp_ctx, NULL,
						   "neutralize nt4 emulation",
						   server_netbios_domain,
						   neutralize_nt4_emulation);

	proposed_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
	proposed_flags |= NETLOGON_NEG_SUPPORTS_AES;

	switch (type) {
	case SEC_CHAN_WKSTA:
		if (lpcfg_security(lp_ctx) == SEC_ADS) {
			/*
			 * AD domains should be secure
			 */
			required_flags |= NETLOGON_NEG_PASSWORD_SET2;
			require_sign_or_seal = true;
			require_strong_key = true;
		}
		break;

	case SEC_CHAN_DOMAIN:
		break;

	case SEC_CHAN_DNS_DOMAIN:
		/*
		 * AD domains should be secure
		 */
		required_flags |= NETLOGON_NEG_PASSWORD_SET2;
		require_sign_or_seal = true;
		require_strong_key = true;
		neutralize_nt4_emulation = true;
		break;

	case SEC_CHAN_BDC:
		required_flags |= NETLOGON_NEG_PASSWORD_SET2;
		require_sign_or_seal = true;
		require_strong_key = true;
		break;

	case SEC_CHAN_RODC:
		required_flags |= NETLOGON_NEG_RODC_PASSTHROUGH;
		required_flags |= NETLOGON_NEG_PASSWORD_SET2;
		require_sign_or_seal = true;
		require_strong_key = true;
		neutralize_nt4_emulation = true;
		break;

	default:
		TALLOC_FREE(frame);
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (neutralize_nt4_emulation) {
		proposed_flags |= NETLOGON_NEG_NEUTRALIZE_NT4_EMULATION;
	}

	if (require_sign_or_seal) {
		required_flags |= NETLOGON_NEG_ARCFOUR;
		required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC;
	} else {
		proposed_flags &= ~NETLOGON_NEG_AUTHENTICATED_RPC;
	}

	if (reject_md5_servers) {
		required_flags |= NETLOGON_NEG_ARCFOUR;
		required_flags |= NETLOGON_NEG_PASSWORD_SET2;
		required_flags |= NETLOGON_NEG_SUPPORTS_AES;
		required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC;
	}

	if (require_strong_key) {
		required_flags |= NETLOGON_NEG_ARCFOUR;
		required_flags |= NETLOGON_NEG_STRONG_KEYS;
		required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC;
	}

	/*
	 * If weak crypto is disabled, do not announce that we support RC4 and
	 * require AES.
	 */
	if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED) {
		required_flags |= NETLOGON_NEG_SUPPORTS_AES;
	}

	proposed_flags |= required_flags;

	if (required_flags & NETLOGON_NEG_SUPPORTS_AES) {
		required_flags &= ~NETLOGON_NEG_ARCFOUR;
		required_flags &= ~NETLOGON_NEG_STRONG_KEYS;
	}

	if (seal_secure_channel) {
		auth_level = DCERPC_AUTH_LEVEL_PRIVACY;
	} else {
		auth_level = DCERPC_AUTH_LEVEL_INTEGRITY;
	}

	status = netlogon_creds_cli_context_common(client_computer,
						   client_account,
						   type,
						   auth_level,
						   proposed_flags,
						   required_flags,
						   server_computer,
						   server_netbios_domain,
						   "",
						   mem_ctx,
						   &context);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(frame);
		return status;
	}

	context->db.g_ctx = g_lock_ctx_init(context, msg_ctx);
	if (context->db.g_ctx == NULL) {
		TALLOC_FREE(context);
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	status = netlogon_creds_cli_open_global_db(lp_ctx);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(context);
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	context->db.ctx = netlogon_creds_cli_global_db;
	*_context = context;
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_bind_cli_credentials(
	struct netlogon_creds_cli_context *context, TALLOC_CTX *mem_ctx,
	struct cli_credentials **pcli_creds)
{
	struct cli_credentials *cli_creds;
	struct netlogon_creds_CredentialState *ncreds;
	NTSTATUS status;

	cli_creds = cli_credentials_init(mem_ctx);
	if (cli_creds == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	cli_credentials_set_secure_channel_type(cli_creds,
						context->client.type);
	cli_credentials_set_username(cli_creds, context->client.account,
				     CRED_SPECIFIED);
	cli_credentials_set_domain(cli_creds, context->server.netbios_domain,
				   CRED_SPECIFIED);
	cli_credentials_set_realm(cli_creds, context->server.dns_domain,
				  CRED_SPECIFIED);

	status = netlogon_creds_cli_get(context, cli_creds, &ncreds);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(cli_creds);
		return status;
	}
	cli_credentials_set_netlogon_creds(cli_creds, ncreds);

	*pcli_creds = cli_creds;
	return NT_STATUS_OK;
}

char *netlogon_creds_cli_debug_string(
		const struct netlogon_creds_cli_context *context,
		TALLOC_CTX *mem_ctx)
{
	return talloc_asprintf(mem_ctx, "netlogon_creds_cli:%s",
			       context->db.key_name);
}

enum dcerpc_AuthLevel netlogon_creds_cli_auth_level(
		struct netlogon_creds_cli_context *context)
{
	return context->client.auth_level;
}

static bool netlogon_creds_cli_downgraded(uint32_t negotiated_flags,
					  uint32_t proposed_flags,
					  uint32_t required_flags)
{
	uint32_t req_flags = required_flags;
	uint32_t tmp_flags;

	req_flags = required_flags;
	if ((negotiated_flags & NETLOGON_NEG_SUPPORTS_AES) &&
	    (proposed_flags & NETLOGON_NEG_SUPPORTS_AES))
	{
		req_flags &= ~NETLOGON_NEG_ARCFOUR|NETLOGON_NEG_STRONG_KEYS;
	}

	tmp_flags = negotiated_flags;
	tmp_flags &= req_flags;
	if (tmp_flags != req_flags) {
		return true;
	}

	return false;
}

struct netlogon_creds_cli_fetch_state {
	TALLOC_CTX *mem_ctx;
	struct netlogon_creds_CredentialState *creds;
	uint32_t proposed_flags;
	uint32_t required_flags;
	NTSTATUS status;
};

static void netlogon_creds_cli_fetch_parser(TDB_DATA key, TDB_DATA data,
					    void *private_data)
{
	struct netlogon_creds_cli_fetch_state *state =
		(struct netlogon_creds_cli_fetch_state *)private_data;
	enum ndr_err_code ndr_err;
	DATA_BLOB blob;
	bool downgraded;

	state->creds = talloc_zero(state->mem_ctx,
				   struct netlogon_creds_CredentialState);
	if (state->creds == NULL) {
		state->status = NT_STATUS_NO_MEMORY;
		return;
	}

	blob.data = data.dptr;
	blob.length = data.dsize;

	ndr_err = ndr_pull_struct_blob(&blob, state->creds, state->creds,
		(ndr_pull_flags_fn_t)ndr_pull_netlogon_creds_CredentialState);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		TALLOC_FREE(state->creds);
		state->status = ndr_map_error2ntstatus(ndr_err);
		return;
	}

	if (DEBUGLEVEL >= 10) {
		NDR_PRINT_DEBUG(netlogon_creds_CredentialState, state->creds);
	}

	downgraded = netlogon_creds_cli_downgraded(
			state->creds->negotiate_flags,
			state->proposed_flags,
			state->required_flags);
	if (downgraded) {
		TALLOC_FREE(state->creds);
		state->status = NT_STATUS_DOWNGRADE_DETECTED;
		return;
	}

	state->status = NT_STATUS_OK;
}

static NTSTATUS netlogon_creds_cli_get_internal(
	struct netlogon_creds_cli_context *context,
	TALLOC_CTX *mem_ctx, struct netlogon_creds_CredentialState **pcreds);

NTSTATUS netlogon_creds_cli_get(struct netlogon_creds_cli_context *context,
				TALLOC_CTX *mem_ctx,
				struct netlogon_creds_CredentialState **_creds)
{
	NTSTATUS status;
	struct netlogon_creds_CredentialState *creds;

	*_creds = NULL;

	status = netlogon_creds_cli_get_internal(context, mem_ctx, &creds);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	/*
	 * mark it as invalid for step operations.
	 */
	creds->sequence = 0;
	creds->seed = (struct netr_Credential) {{0}};
	creds->client = (struct netr_Credential) {{0}};
	creds->server = (struct netr_Credential) {{0}};

	*_creds = creds;
	return NT_STATUS_OK;
}

bool netlogon_creds_cli_validate(struct netlogon_creds_cli_context *context,
			const struct netlogon_creds_CredentialState *creds1)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct netlogon_creds_CredentialState *creds2;
	DATA_BLOB blob1;
	DATA_BLOB blob2;
	NTSTATUS status;
	enum ndr_err_code ndr_err;
	bool equal;

	status = netlogon_creds_cli_get(context, frame, &creds2);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(frame);
		return false;
	}

	ndr_err = ndr_push_struct_blob(&blob1, frame, creds1,
		(ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		TALLOC_FREE(frame);
		return false;
	}

	ndr_err = ndr_push_struct_blob(&blob2, frame, creds2,
		(ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		TALLOC_FREE(frame);
		return false;
	}

	equal = data_blob_equal_const_time(&blob1, &blob2);

	TALLOC_FREE(frame);

	return equal;
}

static NTSTATUS netlogon_creds_cli_store_internal(
	struct netlogon_creds_cli_context *context,
	struct netlogon_creds_CredentialState *creds)
{
	TALLOC_CTX *frame = talloc_stackframe();
	NTSTATUS status;
	enum ndr_err_code ndr_err;
	DATA_BLOB blob;
	TDB_DATA data;
	struct netlogon_creds_CredentialState_legacy lc = { .sequence = 0, };

	if (DEBUGLEVEL >= 10) {
		NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds);
	}

	ndr_err = ndr_push_struct_blob(&blob, frame, creds,
		(ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		status = ndr_map_error2ntstatus(ndr_err);
		TALLOC_FREE(frame);
		return status;
	}

	ndr_err = ndr_pull_struct_blob_all(&blob, frame, &lc,
		(ndr_pull_flags_fn_t)ndr_pull_netlogon_creds_CredentialState_legacy);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		status = ndr_map_error2ntstatus(ndr_err);
		TALLOC_FREE(frame);
		return status;
	}

	if (DEBUGLEVEL >= 11) {
		NDR_PRINT_DEBUG(netlogon_creds_CredentialState_legacy, &lc);
	}

	data.dptr = blob.data;
	data.dsize = blob.length;

	status = dbwrap_store(context->db.ctx,
			      context->db.key_data,
			      data, TDB_REPLACE);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(frame);
		return status;
	}

	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_store(struct netlogon_creds_cli_context *context,
				  struct netlogon_creds_CredentialState *creds)
{
	NTSTATUS status;

	if (context->db.locked_state == NULL) {
		/*
		 * this was not the result of netlogon_creds_cli_lock*()
		 */
		return NT_STATUS_INVALID_PAGE_PROTECTION;
	}

	if (context->db.locked_state->creds != creds) {
		/*
		 * this was not the result of netlogon_creds_cli_lock*()
		 */
		return NT_STATUS_INVALID_PAGE_PROTECTION;
	}

	status = netlogon_creds_cli_store_internal(context, creds);
	return status;
}

static NTSTATUS netlogon_creds_cli_delete_internal(
	struct netlogon_creds_cli_context *context)
{
	NTSTATUS status;
	status = dbwrap_delete(context->db.ctx, context->db.key_data);
	return status;
}

NTSTATUS netlogon_creds_cli_delete_lck(
	struct netlogon_creds_cli_context *context)
{
	NTSTATUS status;

	if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) {
		return NT_STATUS_NOT_LOCKED;
	}

	status = netlogon_creds_cli_delete_internal(context);
	return status;
}

NTSTATUS netlogon_creds_cli_delete(struct netlogon_creds_cli_context *context,
				   struct netlogon_creds_CredentialState *creds)
{
	NTSTATUS status;

	if (context->db.locked_state == NULL) {
		/*
		 * this was not the result of netlogon_creds_cli_lock*()
		 */
		return NT_STATUS_INVALID_PAGE_PROTECTION;
	}

	if (context->db.locked_state->creds != creds) {
		/*
		 * this was not the result of netlogon_creds_cli_lock*()
		 */
		return NT_STATUS_INVALID_PAGE_PROTECTION;
	}

	status = netlogon_creds_cli_delete_internal(context);
	return status;
}

struct netlogon_creds_cli_lock_state {
	struct netlogon_creds_cli_locked_state *locked_state;
	struct netlogon_creds_CredentialState *creds;
};

static void netlogon_creds_cli_lock_done(struct tevent_req *subreq);

struct tevent_req *netlogon_creds_cli_lock_send(TALLOC_CTX *mem_ctx,
				struct tevent_context *ev,
				struct netlogon_creds_cli_context *context)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_lock_state *state;
	struct netlogon_creds_cli_locked_state *locked_state;
	struct tevent_req *subreq;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_lock_state);
	if (req == NULL) {
		return NULL;
	}

	if (context->db.locked_state != NULL) {
		tevent_req_nterror(req, NT_STATUS_LOCK_NOT_GRANTED);
		return tevent_req_post(req, ev);
	}

	locked_state = talloc_zero(state, struct netlogon_creds_cli_locked_state);
	if (tevent_req_nomem(locked_state, req)) {
		return tevent_req_post(req, ev);
	}
	talloc_set_destructor(locked_state,
			      netlogon_creds_cli_locked_state_destructor);
	locked_state->context = context;

	context->db.locked_state = locked_state;
	state->locked_state = locked_state;

	if (context->db.g_ctx == NULL) {
		NTSTATUS status;

		status = netlogon_creds_cli_get_internal(
			context, state, &state->creds);
		if (tevent_req_nterror(req, status)) {
			return tevent_req_post(req, ev);
		}

		return req;
	}

	subreq = g_lock_lock_send(state, ev,
				  context->db.g_ctx,
				  string_term_tdb_data(context->db.key_name),
				  G_LOCK_WRITE,
				  NULL, NULL);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, netlogon_creds_cli_lock_done, req);

	return req;
}

static void netlogon_creds_cli_lock_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_lock_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_lock_state);
	NTSTATUS status;

	status = g_lock_lock_recv(subreq);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	state->locked_state->is_glocked = true;

	status = netlogon_creds_cli_get_internal(state->locked_state->context,
					       state, &state->creds);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	tevent_req_done(req);
}

static NTSTATUS netlogon_creds_cli_get_internal(
	struct netlogon_creds_cli_context *context,
	TALLOC_CTX *mem_ctx, struct netlogon_creds_CredentialState **pcreds)
{
	struct netlogon_creds_cli_fetch_state fstate = {
		.status = NT_STATUS_INTERNAL_ERROR,
		.proposed_flags = context->client.proposed_flags,
		.required_flags = context->client.required_flags,
	};
	NTSTATUS status;

	fstate.mem_ctx = mem_ctx;
	status = dbwrap_parse_record(context->db.ctx,
				     context->db.key_data,
				     netlogon_creds_cli_fetch_parser,
				     &fstate);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}
	if (!NT_STATUS_IS_OK(fstate.status)) {
		return fstate.status;
	}

	if (context->server.cached_flags == fstate.creds->negotiate_flags) {
		*pcreds = fstate.creds;
		return NT_STATUS_OK;
	}

	/*
	 * It is really important to try SamLogonEx here,
	 * because multiple processes can talk to the same
	 * domain controller, without using the credential
	 * chain.
	 *
	 * With a normal SamLogon call, we must keep the
	 * credentials chain updated and intact between all
	 * users of the machine account (which would imply
	 * cross-node communication for every NTLM logon).
	 *
	 * The credentials chain is not per NETLOGON pipe
	 * connection, but globally on the server/client pair
	 * by computer name.
	 *
	 * It's also important to use NetlogonValidationSamInfo4 (6),
	 * because it relies on the rpc transport encryption
	 * and avoids using the global netlogon schannel
	 * session key to en/decrypt secret information
	 * like the user_session_key for network logons.
	 *
	 * [MS-APDS] 3.1.5.2 NTLM Network Logon
	 * says NETLOGON_NEG_CROSS_FOREST_TRUSTS and
	 * NETLOGON_NEG_AUTHENTICATED_RPC set together
	 * are the indication that the server supports
	 * NetlogonValidationSamInfo4 (6). And it must only
	 * be used if "SealSecureChannel" is used.
	 *
	 * The "SealSecureChannel" AUTH_TYPE_SCHANNEL/AUTH_LEVEL_PRIVACY
	 * check is done in netlogon_creds_cli_LogonSamLogon*().
	 */

	context->server.cached_flags = fstate.creds->negotiate_flags;
	context->server.try_validation6 = true;
	context->server.try_logon_ex = true;
	context->server.try_logon_with = true;

	if (!(context->server.cached_flags & NETLOGON_NEG_AUTHENTICATED_RPC)) {
		context->server.try_validation6 = false;
		context->server.try_logon_ex = false;
	}
	if (!(context->server.cached_flags & NETLOGON_NEG_CROSS_FOREST_TRUSTS)) {
		context->server.try_validation6 = false;
	}

	*pcreds = fstate.creds;
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_lock_recv(struct tevent_req *req,
			TALLOC_CTX *mem_ctx,
			struct netlogon_creds_CredentialState **creds)
{
	struct netlogon_creds_cli_lock_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_lock_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		tevent_req_received(req);
		return status;
	}

	talloc_steal(state->creds, state->locked_state);
	state->locked_state->creds = state->creds;
	*creds = talloc_move(mem_ctx, &state->creds);
	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_lock(struct netlogon_creds_cli_context *context,
			TALLOC_CTX *mem_ctx,
			struct netlogon_creds_CredentialState **creds)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_lock_send(frame, ev, context);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_lock_recv(req, mem_ctx, creds);
 fail:
	TALLOC_FREE(frame);
	return status;
}

struct netlogon_creds_cli_lck {
	struct netlogon_creds_cli_context *context;
};

struct netlogon_creds_cli_lck_state {
	struct netlogon_creds_cli_lck *lck;
	enum netlogon_creds_cli_lck_type type;
};

static void netlogon_creds_cli_lck_locked(struct tevent_req *subreq);
static int netlogon_creds_cli_lck_destructor(
	struct netlogon_creds_cli_lck *lck);

struct tevent_req *netlogon_creds_cli_lck_send(
	TALLOC_CTX *mem_ctx, struct tevent_context *ev,
	struct netlogon_creds_cli_context *context,
	enum netlogon_creds_cli_lck_type type)
{
	struct tevent_req *req, *subreq;
	struct netlogon_creds_cli_lck_state *state;
	enum g_lock_type gtype;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_lck_state);
	if (req == NULL) {
		return NULL;
	}

	if (context->db.lock != NETLOGON_CREDS_CLI_LCK_NONE) {
		DBG_DEBUG("context already locked\n");
		tevent_req_nterror(req, NT_STATUS_INVALID_LOCK_SEQUENCE);
		return tevent_req_post(req, ev);
	}

	switch (type) {
	    case NETLOGON_CREDS_CLI_LCK_SHARED:
		    gtype = G_LOCK_READ;
		    break;
	    case NETLOGON_CREDS_CLI_LCK_EXCLUSIVE:
		    gtype = G_LOCK_WRITE;
		    break;
	    default:
		    tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
		    return tevent_req_post(req, ev);
	}

	state->lck = talloc(state, struct netlogon_creds_cli_lck);
	if (tevent_req_nomem(state->lck, req)) {
		return tevent_req_post(req, ev);
	}
	state->lck->context = context;
	state->type = type;

	subreq = g_lock_lock_send(state, ev,
				  context->db.g_ctx,
				  string_term_tdb_data(context->db.key_name),
				  gtype,
				  NULL, NULL);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}
	tevent_req_set_callback(subreq, netlogon_creds_cli_lck_locked, req);

	return req;
}

static void netlogon_creds_cli_lck_locked(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct netlogon_creds_cli_lck_state *state = tevent_req_data(
		req, struct netlogon_creds_cli_lck_state);
	NTSTATUS status;

	status = g_lock_lock_recv(subreq);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	state->lck->context->db.lock = state->type;
	talloc_set_destructor(state->lck, netlogon_creds_cli_lck_destructor);

	tevent_req_done(req);
}

static int netlogon_creds_cli_lck_destructor(
	struct netlogon_creds_cli_lck *lck)
{
	struct netlogon_creds_cli_context *ctx = lck->context;
	NTSTATUS status;

	status = g_lock_unlock(ctx->db.g_ctx,
			       string_term_tdb_data(ctx->db.key_name));
	if (!NT_STATUS_IS_OK(status)) {
		DBG_WARNING("g_lock_unlock failed: %s\n", nt_errstr(status));
		smb_panic("g_lock_unlock failed");
	}
	ctx->db.lock = NETLOGON_CREDS_CLI_LCK_NONE;
	return 0;
}

NTSTATUS netlogon_creds_cli_lck_recv(
	struct tevent_req *req, TALLOC_CTX *mem_ctx,
	struct netlogon_creds_cli_lck **lck)
{
	struct netlogon_creds_cli_lck_state *state = tevent_req_data(
		req, struct netlogon_creds_cli_lck_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		return status;
	}
	*lck = talloc_move(mem_ctx, &state->lck);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_lck(
	struct netlogon_creds_cli_context *context,
	enum netlogon_creds_cli_lck_type type,
	TALLOC_CTX *mem_ctx, struct netlogon_creds_cli_lck **lck)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_lck_send(frame, ev, context, type);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_lck_recv(req, mem_ctx, lck);
 fail:
	TALLOC_FREE(frame);
	return status;
}

static NTSTATUS netlogon_creds_cli_check_transport(
		enum dcerpc_AuthType auth_type,
		enum dcerpc_AuthLevel auth_level,
		const struct netlogon_creds_CredentialState *creds,
		enum dcerpc_AuthLevel min_auth_level)
{
	if (auth_level < min_auth_level) {
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	if (creds == NULL) {
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	if (auth_type == DCERPC_AUTH_TYPE_SCHANNEL) {
		switch (auth_level) {
		case DCERPC_AUTH_LEVEL_INTEGRITY:
		case DCERPC_AUTH_LEVEL_PRIVACY:
			return NT_STATUS_OK;
		default:
			break;
		}

		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	if (creds->negotiate_flags & NETLOGON_NEG_AUTHENTICATED_RPC) {
		/*
		 * if DCERPC_AUTH_TYPE_SCHANNEL is supported
		 * it should be used, which means
		 * we had a chance to verify no downgrade
		 * happened.
		 *
		 * This relies on netlogon_creds_cli_check*
		 * being called before, as first request after
		 * the DCERPC bind.
		 */
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	return NT_STATUS_OK;
}

struct netlogon_creds_cli_auth_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;
	uint8_t num_nt_hashes;
	uint8_t idx_nt_hashes;
	const struct samr_Password * const *nt_hashes;
	const struct samr_Password *used_nt_hash;
	char *srv_name_slash;
	uint32_t current_flags;
	struct netr_Credential client_challenge;
	struct netr_Credential server_challenge;
	struct netlogon_creds_CredentialState *creds;
	struct netr_Credential client_credential;
	struct netr_Credential server_credential;
	uint32_t negotiate_flags;
	uint32_t rid;
	bool try_auth3;
	bool try_auth2;
	bool require_auth2;
};

static void netlogon_creds_cli_auth_challenge_start(struct tevent_req *req);

struct tevent_req *netlogon_creds_cli_auth_send(TALLOC_CTX *mem_ctx,
				struct tevent_context *ev,
				struct netlogon_creds_cli_context *context,
				struct dcerpc_binding_handle *b,
				uint8_t num_nt_hashes,
				const struct samr_Password * const *nt_hashes)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_auth_state *state;
	NTSTATUS status;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_auth_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;
	if (num_nt_hashes < 1) {
		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
		return tevent_req_post(req, ev);
	}
	if (num_nt_hashes > 4) {
		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
		return tevent_req_post(req, ev);
	}

	state->num_nt_hashes = num_nt_hashes;
	state->idx_nt_hashes = 0;
	state->nt_hashes = nt_hashes;

	if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) {
		tevent_req_nterror(req, NT_STATUS_NOT_LOCKED);
		return tevent_req_post(req, ev);
	}

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&state->auth_type,
					&state->auth_level);

	state->try_auth3 = true;
	state->try_auth2 = true;

	if (context->client.required_flags != 0) {
		state->require_auth2 = true;
	}

	state->used_nt_hash = state->nt_hashes[state->idx_nt_hashes];
	state->current_flags = context->client.proposed_flags;

	status = dbwrap_purge(state->context->db.ctx,
			      state->context->db.key_data);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	netlogon_creds_cli_auth_challenge_start(req);
	if (!tevent_req_is_in_progress(req)) {
		return tevent_req_post(req, ev);
	}

	return req;
}

static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq);

static void netlogon_creds_cli_auth_challenge_start(struct tevent_req *req)
{
	struct netlogon_creds_cli_auth_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_auth_state);
	struct tevent_req *subreq;

	TALLOC_FREE(state->creds);

	netlogon_creds_random_challenge(&state->client_challenge);

	subreq = dcerpc_netr_ServerReqChallenge_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.computer,
						&state->client_challenge,
						&state->server_challenge);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}
	tevent_req_set_callback(subreq,
				netlogon_creds_cli_auth_challenge_done,
				req);
}

static void netlogon_creds_cli_auth_srvauth_done(struct tevent_req *subreq);

static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_auth_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_auth_state);
	NTSTATUS status;
	NTSTATUS result;

	status = dcerpc_netr_ServerReqChallenge_recv(subreq, state, &result);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	if (tevent_req_nterror(req, result)) {
		return;
	}

	if (!state->try_auth3 && !state->try_auth2) {
		state->current_flags = 0;
	}

	/* Calculate the session key and client credentials */

	state->creds = netlogon_creds_client_init(state,
						  state->context->client.account,
						  state->context->client.computer,
						  state->context->client.type,
						  &state->client_challenge,
						  &state->server_challenge,
						  state->used_nt_hash,
						  &state->client_credential,
						  state->context->client.proposed_flags,
						  state->current_flags);
	if (tevent_req_nomem(state->creds, req)) {
		return;
	}

	if (state->try_auth3) {
		/*
		 * We always need to send our proposed flags,
		 * even if state->current_flags we passed to
		 * netlogon_creds_client_init() is already downgraded,
		 *
		 * An old server will just ignore the bits it doesn't
		 * know about, but LogonGetCapabilities(level=2) will
		 * report what we proposed.
		 */
		state->negotiate_flags = state->context->client.proposed_flags;

		subreq = dcerpc_netr_ServerAuthenticate3_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.account,
						state->context->client.type,
						state->context->client.computer,
						&state->client_credential,
						&state->server_credential,
						&state->negotiate_flags,
						&state->rid);
		if (tevent_req_nomem(subreq, req)) {
			return;
		}
	} else if (state->try_auth2) {
		/*
		 * We always need to send our proposed flags,
		 * even if state->current_flags we passed to
		 * netlogon_creds_client_init() is already downgraded,
		 *
		 * An old server will just ignore the bits it doesn't
		 * know about, but LogonGetCapabilities(level=2) will
		 * report what we proposed.
		 */
		state->negotiate_flags = state->context->client.proposed_flags;
		state->rid = 0;

		subreq = dcerpc_netr_ServerAuthenticate2_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.account,
						state->context->client.type,
						state->context->client.computer,
						&state->client_credential,
						&state->server_credential,
						&state->negotiate_flags);
		if (tevent_req_nomem(subreq, req)) {
			return;
		}
	} else {
		state->negotiate_flags = 0;
		state->rid = 0;

		subreq = dcerpc_netr_ServerAuthenticate_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.account,
						state->context->client.type,
						state->context->client.computer,
						&state->client_credential,
						&state->server_credential);
		if (tevent_req_nomem(subreq, req)) {
			return;
		}
	}
	tevent_req_set_callback(subreq,
				netlogon_creds_cli_auth_srvauth_done,
				req);
}

static void netlogon_creds_cli_auth_srvauth_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_auth_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_auth_state);
	NTSTATUS status;
	NTSTATUS result;
	bool downgraded;

	if (state->try_auth3) {
		status = dcerpc_netr_ServerAuthenticate3_recv(subreq, state,
							      &result);
		TALLOC_FREE(subreq);
		if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
			state->try_auth3 = false;
			netlogon_creds_cli_auth_challenge_start(req);
			return;
		}
		if (tevent_req_nterror(req, status)) {
			return;
		}
	} else if (state->try_auth2) {
		status = dcerpc_netr_ServerAuthenticate2_recv(subreq, state,
							      &result);
		TALLOC_FREE(subreq);
		if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
			state->try_auth2 = false;
			if (state->require_auth2) {
				status = NT_STATUS_DOWNGRADE_DETECTED;
				tevent_req_nterror(req, status);
				return;
			}
			netlogon_creds_cli_auth_challenge_start(req);
			return;
		}
		if (tevent_req_nterror(req, status)) {
			return;
		}
	} else {
		status = dcerpc_netr_ServerAuthenticate_recv(subreq, state,
							     &result);
		TALLOC_FREE(subreq);
		if (tevent_req_nterror(req, status)) {
			return;
		}
	}

	if (!NT_STATUS_IS_OK(result) &&
	    !NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED))
	{
		tevent_req_nterror(req, result);
		return;
	}

	downgraded = netlogon_creds_cli_downgraded(
			state->negotiate_flags,
			state->context->client.proposed_flags,
			state->context->client.required_flags);
	if (downgraded) {
		if (NT_STATUS_IS_OK(result)) {
			tevent_req_nterror(req, NT_STATUS_DOWNGRADE_DETECTED);
			return;
		}
		tevent_req_nterror(req, result);
		return;
	}

	if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) {
		uint32_t prop_f = state->context->client.proposed_flags;
		uint32_t cli_f = state->current_flags;
		uint32_t srv_f = state->negotiate_flags;
		uint32_t nego_f = cli_f & srv_f;

		if (cli_f == prop_f && nego_f != prop_f) {
			/*
			 * lets retry with the negotiated flags
			 */
			state->current_flags = nego_f;
			netlogon_creds_cli_auth_challenge_start(req);
			return;
		}

		state->idx_nt_hashes += 1;
		if (state->idx_nt_hashes >= state->num_nt_hashes) {
			/*
			 * we already retried, giving up...
			 */
			tevent_req_nterror(req, result);
			return;
		}

		/*
		 * lets retry with the old nt hash.
		 */
		state->used_nt_hash = state->nt_hashes[state->idx_nt_hashes];
		state->current_flags = state->context->client.proposed_flags;
		netlogon_creds_cli_auth_challenge_start(req);
		return;
	}

	status = netlogon_creds_client_verify(state->creds,
					      &state->server_credential,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	if (state->current_flags == state->context->client.proposed_flags) {
		/*
		 * Without a downgrade in the crypto we proposed
		 * we can adjust the otherwise downgraded flags
		 * before storing.
		 */
		state->creds->negotiate_flags &= state->negotiate_flags;
	} else if (state->current_flags != state->negotiate_flags) {
		/*
		 * We downgraded our crypto once, we should not
		 * allow any additional downgrade!
		 */
		tevent_req_nterror(req, NT_STATUS_DOWNGRADE_DETECTED);
		return;
	}

	state->creds->ex->client_sid.sub_auths[0] = state->rid;
	status = netlogon_creds_cli_store_internal(state->context,
						   state->creds);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_auth_recv(struct tevent_req *req,
				      uint8_t *idx_nt_hashes)
{
	struct netlogon_creds_cli_auth_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_auth_state);
	NTSTATUS status;

	*idx_nt_hashes = 0;

	if (tevent_req_is_nterror(req, &status)) {
		tevent_req_received(req);
		return status;
	}

	*idx_nt_hashes = state->idx_nt_hashes;
	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_auth(struct netlogon_creds_cli_context *context,
				 struct dcerpc_binding_handle *b,
				 uint8_t num_nt_hashes,
				 const struct samr_Password * const *nt_hashes,
				 uint8_t *idx_nt_hashes)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	*idx_nt_hashes = 0;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_auth_send(frame, ev, context, b,
					   num_nt_hashes, nt_hashes);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_auth_recv(req, idx_nt_hashes);
 fail:
	TALLOC_FREE(frame);
	return status;
}

struct netlogon_creds_cli_check_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;

	char *srv_name_slash;

	union netr_Capabilities caps;
	union netr_Capabilities client_caps;

	struct netlogon_creds_CredentialState *creds;
	struct netr_Authenticator req_auth;
	struct netr_Authenticator rep_auth;

	union netr_CONTROL_QUERY_INFORMATION ctrl_info;
};

static void netlogon_creds_cli_check_cleanup(struct tevent_req *req,
					     NTSTATUS status);
static void netlogon_creds_cli_check_negotiate_caps(struct tevent_req *subreq);
static void netlogon_creds_cli_check_client_caps(struct tevent_req *subreq);

struct tevent_req *netlogon_creds_cli_check_send(TALLOC_CTX *mem_ctx,
				struct tevent_context *ev,
				struct netlogon_creds_cli_context *context,
				struct dcerpc_binding_handle *b)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_check_state *state;
	struct tevent_req *subreq;
	NTSTATUS status;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_check_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;

	if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) {
		tevent_req_nterror(req, NT_STATUS_NOT_LOCKED);
		return tevent_req_post(req, ev);
	}

	status = netlogon_creds_cli_get_internal(context, state,
						 &state->creds);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&state->auth_type,
					&state->auth_level);

	status = netlogon_creds_cli_check_transport(state->auth_type,
						    state->auth_level,
						    state->creds,
						    DCERPC_AUTH_LEVEL_INTEGRITY);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}

	/*
	 * we defer all callbacks in order to cleanup
	 * the database record.
	 */
	tevent_req_defer_callback(req, state->ev);

	status = netlogon_creds_client_authenticator(state->creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		return tevent_req_post(req, ev);
	}
	ZERO_STRUCT(state->rep_auth);

	subreq = dcerpc_netr_LogonGetCapabilities_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.computer,
						&state->req_auth,
						&state->rep_auth,
						1,
						&state->caps);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_check_negotiate_caps,
				req);

	return req;
}

static void netlogon_creds_cli_check_cleanup(struct tevent_req *req,
					     NTSTATUS status)
{
	struct netlogon_creds_cli_check_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_check_state);

	if (state->creds == NULL) {
		return;
	}

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
		TALLOC_FREE(state->creds);
		return;
	}

	netlogon_creds_cli_delete_lck(state->context);
	TALLOC_FREE(state->creds);
}

static void netlogon_creds_cli_check_control_do(struct tevent_req *req);

static void netlogon_creds_cli_check_negotiate_caps(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_check_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_check_state);
	NTSTATUS status;
	NTSTATUS result;

	status = dcerpc_netr_LogonGetCapabilities_recv(subreq, state,
						       &result);
	TALLOC_FREE(subreq);
	if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
		/*
		 * Note that the negotiated flags are already checked
		 * for our required flags after the ServerAuthenticate3/2 call.
		 */
		uint32_t negotiated = state->creds->negotiate_flags;

		if (negotiated & NETLOGON_NEG_SUPPORTS_AES) {
			/*
			 * If we have negotiated NETLOGON_NEG_SUPPORTS_AES
			 * already, we expect this to work!
			 */
			status = NT_STATUS_DOWNGRADE_DETECTED;
			tevent_req_nterror(req, status);
			netlogon_creds_cli_check_cleanup(req, status);
			return;
		}

		if (negotiated & NETLOGON_NEG_STRONG_KEYS) {
			/*
			 * If we have negotiated NETLOGON_NEG_STRONG_KEYS
			 * we expect this to work at least as far as the
			 * NOT_SUPPORTED error handled below!
			 *
			 * NT 4.0 and Old Samba servers are not
			 * allowed without "require strong key = no"
			 */
			status = NT_STATUS_DOWNGRADE_DETECTED;
			tevent_req_nterror(req, status);
			netlogon_creds_cli_check_cleanup(req, status);
			return;
		}

		/*
		 * If we not require NETLOGON_NEG_SUPPORTS_AES or
		 * NETLOGON_NEG_STRONG_KEYS, it's ok to ignore
		 * NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE.
		 *
		 * This is needed against NT 4.0 and old Samba servers.
		 *
		 * As we're using DCERPC_AUTH_TYPE_SCHANNEL with
		 * DCERPC_AUTH_LEVEL_INTEGRITY or DCERPC_AUTH_LEVEL_PRIVACY
		 * we should detect a faked NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE
		 * with the next request as the sequence number processing
		 * gets out of sync.
		 *
		 * So we'll do a LogonControl message to check that...
		 */
		netlogon_creds_cli_check_control_do(req);
		return;
	}
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}

	if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_IMPLEMENTED)) {
		/*
		 * Note that the negotiated flags are already checked
		 * for our required flags after the ServerAuthenticate3/2 call.
		 */
		uint32_t negotiated = state->creds->negotiate_flags;

		if (negotiated & NETLOGON_NEG_SUPPORTS_AES) {
			/*
			 * If we have negotiated NETLOGON_NEG_SUPPORTS_AES
			 * already, we expect this to work!
			 */
			status = NT_STATUS_DOWNGRADE_DETECTED;
			tevent_req_nterror(req, status);
			netlogon_creds_cli_check_cleanup(req, status);
			return;
		}

		/*
		 * This is ok, the server does not support
		 * NETLOGON_NEG_SUPPORTS_AES.
		 *
		 * netr_LogonGetCapabilities() was
		 * netr_LogonDummyRoutine1() before
		 * NETLOGON_NEG_SUPPORTS_AES was invented.
		 */
		netlogon_creds_cli_check_cleanup(req, result);
		tevent_req_done(req);
		return;
	}

	status = netlogon_creds_client_verify(state->creds,
					      &state->rep_auth.cred,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}

	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_check_cleanup(req, result);
		return;
	}

	if (state->caps.server_capabilities != state->creds->negotiate_flags) {
		status = NT_STATUS_DOWNGRADE_DETECTED;
		tevent_req_nterror(req, status);
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}

	/*
	 * This is the key check that makes this check secure.  If we
	 * get OK here (rather than NOT_SUPPORTED), then the server
	 * did support AES. If the server only proposed STRONG_KEYS
	 * and not AES, then it should have failed with
	 * NOT_IMPLEMENTED. We always send AES as a client, so the
	 * server should always have returned it.
	 */
	if (!(state->caps.server_capabilities & NETLOGON_NEG_SUPPORTS_AES)) {
		status = NT_STATUS_DOWNGRADE_DETECTED;
		tevent_req_nterror(req, status);
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}

	status = netlogon_creds_cli_store_internal(state->context,
						   state->creds);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	/*
	 * Now try to verify our client proposed flags
	 * arrived at the server, using query_level = 2
	 */

	status = netlogon_creds_client_authenticator(state->creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	ZERO_STRUCT(state->rep_auth);

	subreq = dcerpc_netr_LogonGetCapabilities_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.computer,
						&state->req_auth,
						&state->rep_auth,
						2,
						&state->client_caps);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_check_client_caps,
				req);
	return;
}

static void netlogon_creds_cli_check_client_caps(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_check_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_check_state);
	uint32_t requested_flags;
	NTSTATUS status;
	NTSTATUS result;

	status = dcerpc_netr_LogonGetCapabilities_recv(subreq, state,
						       &result);
	TALLOC_FREE(subreq);
	if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_BAD_STUB_DATA)) {
		/*
		 * unpatched Samba server, see
		 * https://bugzilla.samba.org/show_bug.cgi?id=15418
		 */
		status = NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE;
	}
	if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) {
		/*
		 * Here we know the negotiated flags were already
		 * verified with query_level=1, which means
		 * the server supported NETLOGON_NEG_SUPPORTS_AES
		 * and also NETLOGON_NEG_AUTHENTICATED_RPC
		 *
		 * As we're using DCERPC_AUTH_TYPE_SCHANNEL with
		 * DCERPC_AUTH_LEVEL_INTEGRITY or DCERPC_AUTH_LEVEL_PRIVACY
		 * we should detect a faked
		 * NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE
		 * with the next request as the sequence number processing
		 * gets out of sync.
		 *
		 * So we'll do a LogonControl message to check that...
		 */
		netlogon_creds_cli_check_control_do(req);
		return;
	}
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}

	status = netlogon_creds_client_verify(state->creds,
					      &state->rep_auth.cred,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}
	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_check_cleanup(req, result);
		return;
	}

	if (state->creds->ex != NULL) {
		requested_flags = state->creds->ex->client_requested_flags;
	} else {
		requested_flags = state->context->client.proposed_flags;
	}

	if (state->client_caps.requested_flags != requested_flags) {
		status = NT_STATUS_DOWNGRADE_DETECTED;
		tevent_req_nterror(req, status);
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}

	status = netlogon_creds_cli_store_internal(state->context,
						   state->creds);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	tevent_req_done(req);
}

static void netlogon_creds_cli_check_control_done(struct tevent_req *subreq);

static void netlogon_creds_cli_check_control_do(struct tevent_req *req)
{
	struct netlogon_creds_cli_check_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_check_state);
	struct tevent_req *subreq = NULL;

	/*
	 * In case we got a downgrade based on a FAULT
	 * we use a LogonControl that is supposed to
	 * return WERR_NOT_SUPPORTED (without a DCERPC FAULT)
	 * to verify that the connection is still ok and didn't
	 * get out of sync.
	 */
	subreq = dcerpc_netr_LogonControl_send(state,
					       state->ev,
					       state->binding_handle,
					       state->srv_name_slash,
					       NETLOGON_CONTROL_QUERY,
					       2,
					       &state->ctrl_info);
	if (tevent_req_nomem(subreq, req)) {
		return;
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_check_control_done,
				req);
	return;
}

static void netlogon_creds_cli_check_control_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_check_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_check_state);
	NTSTATUS status;
	WERROR result;

	status = dcerpc_netr_LogonControl_recv(subreq, state, &result);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		/*
		 * We want to delete the creds,
		 * so we pass NT_STATUS_DOWNGRADE_DETECTED
		 * to netlogon_creds_cli_check_cleanup()
		 */
		status = NT_STATUS_DOWNGRADE_DETECTED;
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}

	if (!W_ERROR_EQUAL(result, WERR_NOT_SUPPORTED)) {
		status = NT_STATUS_DOWNGRADE_DETECTED;
		tevent_req_nterror(req, status);
		netlogon_creds_cli_check_cleanup(req, status);
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_check_recv(struct tevent_req *req,
				       union netr_Capabilities *capabilities)
{
	struct netlogon_creds_cli_check_state *state = tevent_req_data(
		req, struct netlogon_creds_cli_check_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		netlogon_creds_cli_check_cleanup(req, status);
		tevent_req_received(req);
		return status;
	}

	if (capabilities != NULL) {
		*capabilities = state->caps;
	}

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_check(struct netlogon_creds_cli_context *context,
				  struct dcerpc_binding_handle *b,
				  union netr_Capabilities *capabilities)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_check_send(frame, ev, context, b);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_check_recv(req, capabilities);
 fail:
	TALLOC_FREE(frame);
	return status;
}

struct netlogon_creds_cli_ServerPasswordSet_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;
	uint32_t old_timeout;

	char *srv_name_slash;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;

	struct samr_CryptPassword samr_crypt_password;
	struct netr_CryptPassword netr_crypt_password;
	struct samr_Password samr_password;

	struct netlogon_creds_CredentialState *creds;
	struct netlogon_creds_CredentialState tmp_creds;
	struct netr_Authenticator req_auth;
	struct netr_Authenticator rep_auth;
};

static void netlogon_creds_cli_ServerPasswordSet_cleanup(struct tevent_req *req,
						     NTSTATUS status);
static void netlogon_creds_cli_ServerPasswordSet_locked(struct tevent_req *subreq);

struct tevent_req *netlogon_creds_cli_ServerPasswordSet_send(TALLOC_CTX *mem_ctx,
				struct tevent_context *ev,
				struct netlogon_creds_cli_context *context,
				struct dcerpc_binding_handle *b,
				const DATA_BLOB *new_password,
				const uint32_t *new_version)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_ServerPasswordSet_state *state;
	struct tevent_req *subreq;
	bool ok;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_ServerPasswordSet_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;

	if (new_password->length < 14) {
		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
		return tevent_req_post(req, ev);
	}

	/*
	 * netr_ServerPasswordSet
	 */
	mdfour(state->samr_password.hash, new_password->data, new_password->length);

	/*
	 * netr_ServerPasswordSet2
	 */
	ok = set_pw_in_buffer(state->samr_crypt_password.data,
			      new_password);
	if (!ok) {
		tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
		return tevent_req_post(req, ev);
	}

	if (new_version != NULL) {
		struct NL_PASSWORD_VERSION version;
		uint32_t len = IVAL(state->samr_crypt_password.data, 512);
		uint32_t ofs = 512 - len;
		uint8_t *p;

		if (len > 500) {
			tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
			return tevent_req_post(req, ev);
		}
		ofs -= 12;

		version.ReservedField = 0;
		version.PasswordVersionNumber = *new_version;
		version.PasswordVersionPresent =
			NETLOGON_PASSWORD_VERSION_NUMBER_PRESENT;

		p = state->samr_crypt_password.data + ofs;
		SIVAL(p, 0, version.ReservedField);
		SIVAL(p, 4, version.PasswordVersionNumber);
		SIVAL(p, 8, version.PasswordVersionPresent);
	}

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&state->auth_type,
					&state->auth_level);

	subreq = netlogon_creds_cli_lock_send(state, state->ev,
					      state->context);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_ServerPasswordSet_locked,
				req);

	return req;
}

static void netlogon_creds_cli_ServerPasswordSet_cleanup(struct tevent_req *req,
							 NTSTATUS status)
{
	struct netlogon_creds_cli_ServerPasswordSet_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_ServerPasswordSet_state);

	if (state->creds == NULL) {
		return;
	}

	dcerpc_binding_handle_set_timeout(state->binding_handle,
					  state->old_timeout);

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
		TALLOC_FREE(state->creds);
		return;
	}

	netlogon_creds_cli_delete(state->context, state->creds);
	TALLOC_FREE(state->creds);
}

static void netlogon_creds_cli_ServerPasswordSet_done(struct tevent_req *subreq);

static void netlogon_creds_cli_ServerPasswordSet_locked(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_ServerPasswordSet_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_ServerPasswordSet_state);
	NTSTATUS status;

	status = netlogon_creds_cli_lock_recv(subreq, state,
					      &state->creds);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	status = netlogon_creds_cli_check_transport(state->auth_type,
						    state->auth_level,
						    state->creds,
						    DCERPC_AUTH_LEVEL_NONE);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	state->old_timeout = dcerpc_binding_handle_set_timeout(
				state->binding_handle, 600000);

	/*
	 * we defer all callbacks in order to cleanup
	 * the database record.
	 */
	tevent_req_defer_callback(req, state->ev);

	state->tmp_creds = *state->creds;
	status = netlogon_creds_client_authenticator(&state->tmp_creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	ZERO_STRUCT(state->rep_auth);

	if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_PASSWORD_SET2) {

		status = netlogon_creds_encrypt_samr_CryptPassword(&state->tmp_creds,
								   &state->samr_crypt_password,
								   state->auth_type,
								   state->auth_level);
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
			return;
		}

		memcpy(state->netr_crypt_password.data,
		       state->samr_crypt_password.data, 512);
		state->netr_crypt_password.length =
			IVAL(state->samr_crypt_password.data, 512);

		subreq = dcerpc_netr_ServerPasswordSet2_send(state, state->ev,
					state->binding_handle,
					state->srv_name_slash,
					state->tmp_creds.account_name,
					state->tmp_creds.secure_channel_type,
					state->tmp_creds.computer_name,
					&state->req_auth,
					&state->rep_auth,
					&state->netr_crypt_password);
		if (tevent_req_nomem(subreq, req)) {
			status = NT_STATUS_NO_MEMORY;
			netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
			return;
		}
	} else {
		status = netlogon_creds_encrypt_samr_Password(&state->tmp_creds,
							      &state->samr_password,
							      state->auth_type,
							      state->auth_level);
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
			return;
		}

		subreq = dcerpc_netr_ServerPasswordSet_send(state, state->ev,
					state->binding_handle,
					state->srv_name_slash,
					state->tmp_creds.account_name,
					state->tmp_creds.secure_channel_type,
					state->tmp_creds.computer_name,
					&state->req_auth,
					&state->rep_auth,
					&state->samr_password);
		if (tevent_req_nomem(subreq, req)) {
			status = NT_STATUS_NO_MEMORY;
			netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
			return;
		}
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_ServerPasswordSet_done,
				req);
}

static void netlogon_creds_cli_ServerPasswordSet_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_ServerPasswordSet_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_ServerPasswordSet_state);
	NTSTATUS status;
	NTSTATUS result;

	if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_PASSWORD_SET2) {
		status = dcerpc_netr_ServerPasswordSet2_recv(subreq, state,
							     &result);
		TALLOC_FREE(subreq);
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
			return;
		}
	} else {
		status = dcerpc_netr_ServerPasswordSet_recv(subreq, state,
							    &result);
		TALLOC_FREE(subreq);
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
			return;
		}
	}

	status = netlogon_creds_client_verify(&state->tmp_creds,
					      &state->rep_auth.cred,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
		return;
	}

	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_ServerPasswordSet_cleanup(req, result);
		return;
	}

	dcerpc_binding_handle_set_timeout(state->binding_handle,
					  state->old_timeout);

	*state->creds = state->tmp_creds;
	status = netlogon_creds_cli_store(state->context,
					  state->creds);
	TALLOC_FREE(state->creds);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_ServerPasswordSet_recv(struct tevent_req *req)
{
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
		tevent_req_received(req);
		return status;
	}

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_ServerPasswordSet(
				struct netlogon_creds_cli_context *context,
				struct dcerpc_binding_handle *b,
				const DATA_BLOB *new_password,
				const uint32_t *new_version)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_ServerPasswordSet_send(frame, ev, context, b,
							new_password,
							new_version);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_ServerPasswordSet_recv(req);
 fail:
	TALLOC_FREE(frame);
	return status;
}

struct netlogon_creds_cli_LogonSamLogon_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;

	char *srv_name_slash;

	enum netr_LogonInfoClass logon_level;
	const union netr_LogonLevel *const_logon;
	union netr_LogonLevel *logon;
	uint32_t flags;

	uint16_t validation_level;
	union netr_Validation *validation;
	uint8_t authoritative;

	/*
	 * do we need encryption at the application layer?
	 */
	bool user_encrypt;
	bool try_logon_ex;
	bool try_validation6;

	/*
	 * the read only credentials before we started the operation
	 * used for netr_LogonSamLogonEx() if required (validation_level = 3).
	 */
	struct netlogon_creds_CredentialState *ro_creds;

	/*
	 * The (locked) credentials used for the credential chain
	 * used for netr_LogonSamLogonWithFlags() or
	 * netr_LogonSamLogonWith().
	 */
	struct netlogon_creds_CredentialState *lk_creds;

	/*
	 * While we have locked the global credentials (lk_creds above)
	 * we operate an a temporary copy, because a server
	 * may not support netr_LogonSamLogonWithFlags() and
	 * didn't process our netr_Authenticator, so we need to
	 * restart from lk_creds.
	 */
	struct netlogon_creds_CredentialState tmp_creds;
	struct netr_Authenticator req_auth;
	struct netr_Authenticator rep_auth;
};

static void netlogon_creds_cli_LogonSamLogon_start(struct tevent_req *req);
static void netlogon_creds_cli_LogonSamLogon_cleanup(struct tevent_req *req,
						     NTSTATUS status);

struct tevent_req *netlogon_creds_cli_LogonSamLogon_send(TALLOC_CTX *mem_ctx,
				struct tevent_context *ev,
				struct netlogon_creds_cli_context *context,
				struct dcerpc_binding_handle *b,
				enum netr_LogonInfoClass logon_level,
				const union netr_LogonLevel *logon,
				uint32_t flags)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_LogonSamLogon_state *state;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_LogonSamLogon_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;

	state->logon_level = logon_level;
	state->const_logon = logon;
	state->flags = flags;

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	switch (logon_level) {
	case NetlogonInteractiveInformation:
	case NetlogonInteractiveTransitiveInformation:
	case NetlogonServiceInformation:
	case NetlogonServiceTransitiveInformation:
	case NetlogonGenericInformation:
		state->user_encrypt = true;
		break;

	case NetlogonNetworkInformation:
	case NetlogonNetworkTransitiveInformation:
		break;
	}

	state->validation = talloc_zero(state, union netr_Validation);
	if (tevent_req_nomem(state->validation, req)) {
		return tevent_req_post(req, ev);
	}

	netlogon_creds_cli_LogonSamLogon_start(req);
	if (!tevent_req_is_in_progress(req)) {
		return tevent_req_post(req, ev);
	}

	/*
	 * we defer all callbacks in order to cleanup
	 * the database record.
	 */
	tevent_req_defer_callback(req, state->ev);
	return req;
}

static void netlogon_creds_cli_LogonSamLogon_cleanup(struct tevent_req *req,
						     NTSTATUS status)
{
	struct netlogon_creds_cli_LogonSamLogon_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_LogonSamLogon_state);

	if (state->lk_creds == NULL) {
		return;
	}

	if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
		/*
		 * This is a hack to recover from a bug in old
		 * Samba servers, when LogonSamLogonEx() fails:
		 *
		 * api_net_sam_logon_ex: Failed to marshall NET_R_SAM_LOGON_EX.
		 *
		 * All following request will get NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE.
		 *
		 * A second bug generates NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE,
		 * instead of NT_STATUS_ACCESS_DENIED or NT_STATUS_RPC_SEC_PKG_ERROR
		 * If the sign/seal check fails.
		 *
		 * In that case we need to cleanup the netlogon session.
		 *
		 * It's the job of the caller to disconnect the current
		 * connection, if netlogon_creds_cli_LogonSamLogon()
		 * returns NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE.
		 */
		if (!state->context->server.try_logon_with) {
			status = NT_STATUS_NETWORK_ACCESS_DENIED;
		}
	}

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
		TALLOC_FREE(state->lk_creds);
		return;
	}

	netlogon_creds_cli_delete(state->context, state->lk_creds);
	TALLOC_FREE(state->lk_creds);
}

static void netlogon_creds_cli_LogonSamLogon_done(struct tevent_req *subreq);

static void netlogon_creds_cli_LogonSamLogon_start(struct tevent_req *req)
{
	struct netlogon_creds_cli_LogonSamLogon_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_LogonSamLogon_state);
	struct tevent_req *subreq;
	NTSTATUS status;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;

	TALLOC_FREE(state->ro_creds);
	TALLOC_FREE(state->logon);
	ZERO_STRUCTP(state->validation);

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&auth_type, &auth_level);

	state->try_logon_ex = state->context->server.try_logon_ex;
	state->try_validation6 = state->context->server.try_validation6;

	if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) {
		state->try_logon_ex = false;
	}

	if (auth_level != DCERPC_AUTH_LEVEL_PRIVACY) {
		state->try_validation6 = false;
	}

	if (state->try_logon_ex) {
		if (state->try_validation6) {
			state->validation_level = 6;
		} else {
			state->validation_level = 3;
			state->user_encrypt = true;
		}

		state->logon = netlogon_creds_shallow_copy_logon(state,
							state->logon_level,
							state->const_logon);
		if (tevent_req_nomem(state->logon, req)) {
			status = NT_STATUS_NO_MEMORY;
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}

		if (state->user_encrypt) {
			status = netlogon_creds_cli_get(state->context,
							state,
							&state->ro_creds);
			if (!NT_STATUS_IS_OK(status)) {
				status = NT_STATUS_ACCESS_DENIED;
				tevent_req_nterror(req, status);
				netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
				return;
			}

			status = netlogon_creds_encrypt_samlogon_logon(state->ro_creds,
								       state->logon_level,
								       state->logon,
								       auth_type,
								       auth_level);
			if (!NT_STATUS_IS_OK(status)) {
				status = NT_STATUS_ACCESS_DENIED;
				tevent_req_nterror(req, status);
				netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
				return;
			}
		}

		subreq = dcerpc_netr_LogonSamLogonEx_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.computer,
						state->logon_level,
						state->logon,
						state->validation_level,
						state->validation,
						&state->authoritative,
						&state->flags);
		if (tevent_req_nomem(subreq, req)) {
			status = NT_STATUS_NO_MEMORY;
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}
		tevent_req_set_callback(subreq,
					netlogon_creds_cli_LogonSamLogon_done,
					req);
		return;
	}

	if (state->lk_creds == NULL) {
		subreq = netlogon_creds_cli_lock_send(state, state->ev,
						      state->context);
		if (tevent_req_nomem(subreq, req)) {
			status = NT_STATUS_NO_MEMORY;
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}
		tevent_req_set_callback(subreq,
					netlogon_creds_cli_LogonSamLogon_done,
					req);
		return;
	}

	state->tmp_creds = *state->lk_creds;
	status = netlogon_creds_client_authenticator(&state->tmp_creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
		return;
	}
	ZERO_STRUCT(state->rep_auth);

	state->logon = netlogon_creds_shallow_copy_logon(state,
						state->logon_level,
						state->const_logon);
	if (tevent_req_nomem(state->logon, req)) {
		status = NT_STATUS_NO_MEMORY;
		netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
		return;
	}

	status = netlogon_creds_encrypt_samlogon_logon(&state->tmp_creds,
						       state->logon_level,
						       state->logon,
						       auth_type,
						       auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
		return;
	}

	state->validation_level = 3;

	if (state->context->server.try_logon_with) {
		subreq = dcerpc_netr_LogonSamLogonWithFlags_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.computer,
						&state->req_auth,
						&state->rep_auth,
						state->logon_level,
						state->logon,
						state->validation_level,
						state->validation,
						&state->authoritative,
						&state->flags);
		if (tevent_req_nomem(subreq, req)) {
			status = NT_STATUS_NO_MEMORY;
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}
	} else {
		state->flags = 0;

		subreq = dcerpc_netr_LogonSamLogon_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->context->client.computer,
						&state->req_auth,
						&state->rep_auth,
						state->logon_level,
						state->logon,
						state->validation_level,
						state->validation,
						&state->authoritative);
		if (tevent_req_nomem(subreq, req)) {
			status = NT_STATUS_NO_MEMORY;
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_LogonSamLogon_done,
				req);
}

static void netlogon_creds_cli_LogonSamLogon_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_LogonSamLogon_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_LogonSamLogon_state);
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;
	NTSTATUS status;
	NTSTATUS result;
	bool ok;

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&auth_type,
					&auth_level);

	if (state->try_logon_ex) {
		status = dcerpc_netr_LogonSamLogonEx_recv(subreq,
							  state->validation,
							  &result);
		TALLOC_FREE(subreq);
		if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
			state->context->server.try_validation6 = false;
			state->context->server.try_logon_ex = false;
			netlogon_creds_cli_LogonSamLogon_start(req);
			return;
		}
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}

		if ((state->validation_level == 6) &&
		    (NT_STATUS_EQUAL(result, NT_STATUS_INVALID_INFO_CLASS) ||
		     NT_STATUS_EQUAL(result, NT_STATUS_INVALID_PARAMETER) ||
		     NT_STATUS_EQUAL(result, NT_STATUS_BUFFER_TOO_SMALL)))
		{
			state->context->server.try_validation6 = false;
			netlogon_creds_cli_LogonSamLogon_start(req);
			return;
		}

		if (tevent_req_nterror(req, result)) {
			netlogon_creds_cli_LogonSamLogon_cleanup(req, result);
			return;
		}

		if (state->ro_creds == NULL) {
			tevent_req_done(req);
			return;
		}

		ok = netlogon_creds_cli_validate(state->context, state->ro_creds);
		if (!ok) {
			/*
			 * We got a race, lets retry with on authenticator
			 * protection.
			 *
			 * netlogon_creds_cli_LogonSamLogon_start()
			 * will TALLOC_FREE(state->ro_creds);
			 */
			state->try_logon_ex = false;
			netlogon_creds_cli_LogonSamLogon_start(req);
			return;
		}

		status = netlogon_creds_decrypt_samlogon_validation(state->ro_creds,
								    state->validation_level,
								    state->validation,
								    auth_type,
								    auth_level);
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}

		tevent_req_done(req);
		return;
	}

	if (state->lk_creds == NULL) {
		status = netlogon_creds_cli_lock_recv(subreq, state,
						      &state->lk_creds);
		TALLOC_FREE(subreq);
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}

		netlogon_creds_cli_LogonSamLogon_start(req);
		return;
	}

	if (state->context->server.try_logon_with) {
		status = dcerpc_netr_LogonSamLogonWithFlags_recv(subreq,
								 state->validation,
								 &result);
		TALLOC_FREE(subreq);
		if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
			state->context->server.try_logon_with = false;
			netlogon_creds_cli_LogonSamLogon_start(req);
			return;
		}
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}
	} else {
		status = dcerpc_netr_LogonSamLogon_recv(subreq,
							state->validation,
							&result);
		TALLOC_FREE(subreq);
		if (tevent_req_nterror(req, status)) {
			netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
			return;
		}
	}

	status = netlogon_creds_client_verify(&state->tmp_creds,
					      &state->rep_auth.cred,
					      auth_type,
					      auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
		return;
	}

	*state->lk_creds = state->tmp_creds;
	status = netlogon_creds_cli_store(state->context,
					  state->lk_creds);
	TALLOC_FREE(state->lk_creds);

	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
		return;
	}

	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_LogonSamLogon_cleanup(req, result);
		return;
	}

	status = netlogon_creds_decrypt_samlogon_validation(&state->tmp_creds,
							    state->validation_level,
							    state->validation,
							    auth_type,
							    auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_LogonSamLogon_cleanup(req, result);
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_LogonSamLogon_recv(struct tevent_req *req,
					TALLOC_CTX *mem_ctx,
					uint16_t *validation_level,
					union netr_Validation **validation,
					uint8_t *authoritative,
					uint32_t *flags)
{
	struct netlogon_creds_cli_LogonSamLogon_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_LogonSamLogon_state);
	NTSTATUS status;

	/* authoritative is also returned on error */
	*authoritative = state->authoritative;

	if (tevent_req_is_nterror(req, &status)) {
		netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
		tevent_req_received(req);
		return status;
	}

	*validation_level = state->validation_level;
	*validation = talloc_move(mem_ctx, &state->validation);
	*flags = state->flags;

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_LogonSamLogon(
				struct netlogon_creds_cli_context *context,
				struct dcerpc_binding_handle *b,
				enum netr_LogonInfoClass logon_level,
				const union netr_LogonLevel *logon,
				TALLOC_CTX *mem_ctx,
				uint16_t *validation_level,
				union netr_Validation **validation,
				uint8_t *authoritative,
				uint32_t *flags)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_LogonSamLogon_send(frame, ev, context, b,
						    logon_level, logon,
						    *flags);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_LogonSamLogon_recv(req, mem_ctx,
						       validation_level,
						       validation,
						       authoritative,
						       flags);
 fail:
	TALLOC_FREE(frame);
	return status;
}

struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;

	char *srv_name_slash;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;

	const char *site_name;
	uint32_t dns_ttl;
	struct NL_DNS_NAME_INFO_ARRAY *dns_names;

	struct netlogon_creds_CredentialState *creds;
	struct netlogon_creds_CredentialState tmp_creds;
	struct netr_Authenticator req_auth;
	struct netr_Authenticator rep_auth;
};

static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(struct tevent_req *req,
						     NTSTATUS status);
static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked(struct tevent_req *subreq);

struct tevent_req *netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_send(TALLOC_CTX *mem_ctx,
									     struct tevent_context *ev,
									     struct netlogon_creds_cli_context *context,
									     struct dcerpc_binding_handle *b,
									     const char *site_name,
									     uint32_t dns_ttl,
									     struct NL_DNS_NAME_INFO_ARRAY *dns_names)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state;
	struct tevent_req *subreq;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	state->site_name = site_name;
	state->dns_ttl = dns_ttl;
	state->dns_names = dns_names;

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&state->auth_type,
					&state->auth_level);

	subreq = netlogon_creds_cli_lock_send(state, state->ev,
					      state->context);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked,
				req);

	return req;
}

static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(struct tevent_req *req,
							 NTSTATUS status)
{
	struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state);

	if (state->creds == NULL) {
		return;
	}

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
		TALLOC_FREE(state->creds);
		return;
	}

	netlogon_creds_cli_delete(state->context, state->creds);
	TALLOC_FREE(state->creds);
}

static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done(struct tevent_req *subreq);

static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state);
	NTSTATUS status;

	status = netlogon_creds_cli_lock_recv(subreq, state,
					      &state->creds);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	status = netlogon_creds_cli_check_transport(state->auth_type,
						    state->auth_level,
						    state->creds,
						    DCERPC_AUTH_LEVEL_NONE);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	/*
	 * we defer all callbacks in order to cleanup
	 * the database record.
	 */
	tevent_req_defer_callback(req, state->ev);

	state->tmp_creds = *state->creds;
	status = netlogon_creds_client_authenticator(&state->tmp_creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	ZERO_STRUCT(state->rep_auth);

	subreq = dcerpc_netr_DsrUpdateReadOnlyServerDnsRecords_send(state, state->ev,
								    state->binding_handle,
								    state->srv_name_slash,
								    state->tmp_creds.computer_name,
								    &state->req_auth,
								    &state->rep_auth,
								    state->site_name,
								    state->dns_ttl,
								    state->dns_names);
	if (tevent_req_nomem(subreq, req)) {
		status = NT_STATUS_NO_MEMORY;
		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
		return;
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done,
				req);
}

static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state);
	NTSTATUS status;
	NTSTATUS result;

	/*
	 * We use state->dns_names as the memory context, as this is
	 * the only in/out variable and it has been overwritten by the
	 * out parameter from the server.
	 *
	 * We need to preserve the return value until the caller can use it.
	 */
	status = dcerpc_netr_DsrUpdateReadOnlyServerDnsRecords_recv(subreq, state->dns_names,
								    &result);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
		return;
	}

	status = netlogon_creds_client_verify(&state->tmp_creds,
					      &state->rep_auth.cred,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
		return;
	}

	*state->creds = state->tmp_creds;
	status = netlogon_creds_cli_store(state->context,
					  state->creds);
	TALLOC_FREE(state->creds);

	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
		return;
	}

	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, result);
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_recv(struct tevent_req *req)
{
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
		tevent_req_received(req);
		return status;
	}

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords(
				struct netlogon_creds_cli_context *context,
				struct dcerpc_binding_handle *b,
				const char *site_name,
				uint32_t dns_ttl,
				struct NL_DNS_NAME_INFO_ARRAY *dns_names)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_send(frame, ev, context, b,
									site_name,
									dns_ttl,
									dns_names);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_recv(req);
 fail:
	TALLOC_FREE(frame);
	return status;
}

struct netlogon_creds_cli_ServerGetTrustInfo_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;

	char *srv_name_slash;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;

	struct samr_Password new_owf_password;
	struct samr_Password old_owf_password;
	struct netr_TrustInfo *trust_info;

	struct netlogon_creds_CredentialState *creds;
	struct netlogon_creds_CredentialState tmp_creds;
	struct netr_Authenticator req_auth;
	struct netr_Authenticator rep_auth;
};

static void netlogon_creds_cli_ServerGetTrustInfo_cleanup(struct tevent_req *req,
						     NTSTATUS status);
static void netlogon_creds_cli_ServerGetTrustInfo_locked(struct tevent_req *subreq);

struct tevent_req *netlogon_creds_cli_ServerGetTrustInfo_send(TALLOC_CTX *mem_ctx,
					struct tevent_context *ev,
					struct netlogon_creds_cli_context *context,
					struct dcerpc_binding_handle *b)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_ServerGetTrustInfo_state *state;
	struct tevent_req *subreq;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_ServerGetTrustInfo_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&state->auth_type,
					&state->auth_level);

	subreq = netlogon_creds_cli_lock_send(state, state->ev,
					      state->context);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_ServerGetTrustInfo_locked,
				req);

	return req;
}

static void netlogon_creds_cli_ServerGetTrustInfo_cleanup(struct tevent_req *req,
							 NTSTATUS status)
{
	struct netlogon_creds_cli_ServerGetTrustInfo_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_ServerGetTrustInfo_state);

	if (state->creds == NULL) {
		return;
	}

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
		TALLOC_FREE(state->creds);
		return;
	}

	netlogon_creds_cli_delete(state->context, state->creds);
	TALLOC_FREE(state->creds);
}

static void netlogon_creds_cli_ServerGetTrustInfo_done(struct tevent_req *subreq);

static void netlogon_creds_cli_ServerGetTrustInfo_locked(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_ServerGetTrustInfo_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_ServerGetTrustInfo_state);
	NTSTATUS status;

	status = netlogon_creds_cli_lock_recv(subreq, state,
					      &state->creds);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	status = netlogon_creds_cli_check_transport(state->auth_type,
						    state->auth_level,
						    state->creds,
						    DCERPC_AUTH_LEVEL_PRIVACY);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	/*
	 * we defer all callbacks in order to cleanup
	 * the database record.
	 */
	tevent_req_defer_callback(req, state->ev);

	state->tmp_creds = *state->creds;
	status = netlogon_creds_client_authenticator(&state->tmp_creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	ZERO_STRUCT(state->rep_auth);

	subreq = dcerpc_netr_ServerGetTrustInfo_send(state, state->ev,
						     state->binding_handle,
						     state->srv_name_slash,
						     state->tmp_creds.account_name,
						     state->tmp_creds.secure_channel_type,
						     state->tmp_creds.computer_name,
						     &state->req_auth,
						     &state->rep_auth,
						     &state->new_owf_password,
						     &state->old_owf_password,
						     &state->trust_info);
	if (tevent_req_nomem(subreq, req)) {
		status = NT_STATUS_NO_MEMORY;
		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
		return;
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_ServerGetTrustInfo_done,
				req);
}

static void netlogon_creds_cli_ServerGetTrustInfo_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_ServerGetTrustInfo_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_ServerGetTrustInfo_state);
	NTSTATUS status;
	NTSTATUS result;

	/*
	 * We use state->dns_names as the memory context, as this is
	 * the only in/out variable and it has been overwritten by the
	 * out parameter from the server.
	 *
	 * We need to preserve the return value until the caller can use it.
	 */
	status = dcerpc_netr_ServerGetTrustInfo_recv(subreq, state, &result);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
		return;
	}

	status = netlogon_creds_client_verify(&state->tmp_creds,
					      &state->rep_auth.cred,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
		return;
	}

	status = netlogon_creds_decrypt_samr_Password(&state->tmp_creds,
						      &state->new_owf_password,
						      state->auth_type,
						      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
		return;
	}
	status = netlogon_creds_decrypt_samr_Password(&state->tmp_creds,
						      &state->old_owf_password,
						      state->auth_type,
						      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
		return;
	}

	*state->creds = state->tmp_creds;
	status = netlogon_creds_cli_store(state->context,
					  state->creds);
	TALLOC_FREE(state->creds);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
		return;
	}

	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, result);
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_ServerGetTrustInfo_recv(struct tevent_req *req,
					TALLOC_CTX *mem_ctx,
					struct samr_Password *new_owf_password,
					struct samr_Password *old_owf_password,
					struct netr_TrustInfo **trust_info)
{
	struct netlogon_creds_cli_ServerGetTrustInfo_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_ServerGetTrustInfo_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
		tevent_req_received(req);
		return status;
	}

	if (new_owf_password != NULL) {
		*new_owf_password = state->new_owf_password;
	}
	if (old_owf_password != NULL) {
		*old_owf_password = state->old_owf_password;
	}
	if (trust_info != NULL) {
		*trust_info = talloc_move(mem_ctx, &state->trust_info);
	}

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_ServerGetTrustInfo(
				struct netlogon_creds_cli_context *context,
				struct dcerpc_binding_handle *b,
				TALLOC_CTX *mem_ctx,
				struct samr_Password *new_owf_password,
				struct samr_Password *old_owf_password,
				struct netr_TrustInfo **trust_info)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_ServerGetTrustInfo_send(frame, ev, context, b);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_ServerGetTrustInfo_recv(req,
							    mem_ctx,
							    new_owf_password,
							    old_owf_password,
							    trust_info);
 fail:
	TALLOC_FREE(frame);
	return status;
}

struct netlogon_creds_cli_GetForestTrustInformation_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;

	char *srv_name_slash;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;

	uint32_t flags;
	struct lsa_ForestTrustInformation *forest_trust_info;

	struct netlogon_creds_CredentialState *creds;
	struct netlogon_creds_CredentialState tmp_creds;
	struct netr_Authenticator req_auth;
	struct netr_Authenticator rep_auth;
};

static void netlogon_creds_cli_GetForestTrustInformation_cleanup(struct tevent_req *req,
						     NTSTATUS status);
static void netlogon_creds_cli_GetForestTrustInformation_locked(struct tevent_req *subreq);

struct tevent_req *netlogon_creds_cli_GetForestTrustInformation_send(TALLOC_CTX *mem_ctx,
					struct tevent_context *ev,
					struct netlogon_creds_cli_context *context,
					struct dcerpc_binding_handle *b)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_GetForestTrustInformation_state *state;
	struct tevent_req *subreq;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_GetForestTrustInformation_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	state->flags = 0;

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&state->auth_type,
					&state->auth_level);

	subreq = netlogon_creds_cli_lock_send(state, state->ev,
					      state->context);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_GetForestTrustInformation_locked,
				req);

	return req;
}

static void netlogon_creds_cli_GetForestTrustInformation_cleanup(struct tevent_req *req,
							 NTSTATUS status)
{
	struct netlogon_creds_cli_GetForestTrustInformation_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_GetForestTrustInformation_state);

	if (state->creds == NULL) {
		return;
	}

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
		TALLOC_FREE(state->creds);
		return;
	}

	netlogon_creds_cli_delete(state->context, state->creds);
	TALLOC_FREE(state->creds);
}

static void netlogon_creds_cli_GetForestTrustInformation_done(struct tevent_req *subreq);

static void netlogon_creds_cli_GetForestTrustInformation_locked(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_GetForestTrustInformation_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_GetForestTrustInformation_state);
	NTSTATUS status;

	status = netlogon_creds_cli_lock_recv(subreq, state,
					      &state->creds);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	status = netlogon_creds_cli_check_transport(state->auth_type,
						    state->auth_level,
						    state->creds,
						    DCERPC_AUTH_LEVEL_NONE);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	/*
	 * we defer all callbacks in order to cleanup
	 * the database record.
	 */
	tevent_req_defer_callback(req, state->ev);

	state->tmp_creds = *state->creds;
	status = netlogon_creds_client_authenticator(&state->tmp_creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	ZERO_STRUCT(state->rep_auth);

	subreq = dcerpc_netr_GetForestTrustInformation_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->tmp_creds.computer_name,
						&state->req_auth,
						&state->rep_auth,
						state->flags,
						&state->forest_trust_info);
	if (tevent_req_nomem(subreq, req)) {
		status = NT_STATUS_NO_MEMORY;
		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
		return;
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_GetForestTrustInformation_done,
				req);
}

static void netlogon_creds_cli_GetForestTrustInformation_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_GetForestTrustInformation_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_GetForestTrustInformation_state);
	NTSTATUS status;
	NTSTATUS result;

	/*
	 * We use state->dns_names as the memory context, as this is
	 * the only in/out variable and it has been overwritten by the
	 * out parameter from the server.
	 *
	 * We need to preserve the return value until the caller can use it.
	 */
	status = dcerpc_netr_GetForestTrustInformation_recv(subreq, state, &result);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
		return;
	}

	status = netlogon_creds_client_verify(&state->tmp_creds,
					      &state->rep_auth.cred,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
		return;
	}

	*state->creds = state->tmp_creds;
	status = netlogon_creds_cli_store(state->context,
					  state->creds);
	TALLOC_FREE(state->creds);

	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
		return;
	}

	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, result);
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_GetForestTrustInformation_recv(struct tevent_req *req,
			TALLOC_CTX *mem_ctx,
			struct lsa_ForestTrustInformation **forest_trust_info)
{
	struct netlogon_creds_cli_GetForestTrustInformation_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_GetForestTrustInformation_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
		tevent_req_received(req);
		return status;
	}

	*forest_trust_info = talloc_move(mem_ctx, &state->forest_trust_info);

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_GetForestTrustInformation(
			struct netlogon_creds_cli_context *context,
			struct dcerpc_binding_handle *b,
			TALLOC_CTX *mem_ctx,
			struct lsa_ForestTrustInformation **forest_trust_info)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_GetForestTrustInformation_send(frame, ev, context, b);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_GetForestTrustInformation_recv(req,
							mem_ctx,
							forest_trust_info);
 fail:
	TALLOC_FREE(frame);
	return status;
}
struct netlogon_creds_cli_SendToSam_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;

	char *srv_name_slash;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;

	DATA_BLOB opaque;

	struct netlogon_creds_CredentialState *creds;
	struct netlogon_creds_CredentialState tmp_creds;
	struct netr_Authenticator req_auth;
	struct netr_Authenticator rep_auth;
};

static void netlogon_creds_cli_SendToSam_cleanup(struct tevent_req *req,
								 NTSTATUS status);
static void netlogon_creds_cli_SendToSam_locked(struct tevent_req *subreq);

struct tevent_req *netlogon_creds_cli_SendToSam_send(TALLOC_CTX *mem_ctx,
						     struct tevent_context *ev,
						     struct netlogon_creds_cli_context *context,
						     struct dcerpc_binding_handle *b,
						     struct netr_SendToSamBase *message)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_SendToSam_state *state;
	struct tevent_req *subreq;
	enum ndr_err_code ndr_err;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_SendToSam_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	ndr_err = ndr_push_struct_blob(&state->opaque, mem_ctx, message,
				       (ndr_push_flags_fn_t)ndr_push_netr_SendToSamBase);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
		tevent_req_nterror(req, status);
		return tevent_req_post(req, ev);
	}

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&state->auth_type,
					&state->auth_level);

	subreq = netlogon_creds_cli_lock_send(state, state->ev,
					      state->context);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_SendToSam_locked,
				req);

	return req;
}

static void netlogon_creds_cli_SendToSam_cleanup(struct tevent_req *req,
							 NTSTATUS status)
{
	struct netlogon_creds_cli_SendToSam_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_SendToSam_state);

	if (state->creds == NULL) {
		return;
	}

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
		TALLOC_FREE(state->creds);
		return;
	}

	netlogon_creds_cli_delete(state->context, state->creds);
	TALLOC_FREE(state->creds);
}

static void netlogon_creds_cli_SendToSam_done(struct tevent_req *subreq);

static void netlogon_creds_cli_SendToSam_locked(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_SendToSam_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_SendToSam_state);
	NTSTATUS status;

	status = netlogon_creds_cli_lock_recv(subreq, state,
					      &state->creds);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	status = netlogon_creds_cli_check_transport(state->auth_type,
						    state->auth_level,
						    state->creds,
						    DCERPC_AUTH_LEVEL_NONE);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	/*
	 * we defer all callbacks in order to cleanup
	 * the database record.
	 */
	tevent_req_defer_callback(req, state->ev);

	state->tmp_creds = *state->creds;
	status = netlogon_creds_client_authenticator(&state->tmp_creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	ZERO_STRUCT(state->rep_auth);

	status = netlogon_creds_encrypt_SendToSam(&state->tmp_creds,
						  state->opaque.data,
						  state->opaque.length,
						  state->auth_type,
						  state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_SendToSam_cleanup(req, status);
		return;
	}

	subreq = dcerpc_netr_NetrLogonSendToSam_send(state, state->ev,
						     state->binding_handle,
						     state->srv_name_slash,
						     state->tmp_creds.computer_name,
						     &state->req_auth,
						     &state->rep_auth,
						     state->opaque.data,
						     state->opaque.length);
	if (tevent_req_nomem(subreq, req)) {
		status = NT_STATUS_NO_MEMORY;
		netlogon_creds_cli_SendToSam_cleanup(req, status);
		return;
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_SendToSam_done,
				req);
}

static void netlogon_creds_cli_SendToSam_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_SendToSam_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_SendToSam_state);
	NTSTATUS status;
	NTSTATUS result;

	status = dcerpc_netr_NetrLogonSendToSam_recv(subreq, state, &result);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_SendToSam_cleanup(req, status);
		return;
	}

	status = netlogon_creds_client_verify(&state->tmp_creds,
					      &state->rep_auth.cred,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_SendToSam_cleanup(req, status);
		return;
	}

	*state->creds = state->tmp_creds;
	status = netlogon_creds_cli_store(state->context,
					  state->creds);
	TALLOC_FREE(state->creds);

	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_SendToSam_cleanup(req, status);
		return;
	}

	/*
	 * Creds must be stored before we send back application errors
	 * e.g. NT_STATUS_NOT_IMPLEMENTED
	 */
	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_SendToSam_cleanup(req, result);
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_SendToSam_recv(struct tevent_req *req)
{
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		netlogon_creds_cli_SendToSam_cleanup(req, status);
		tevent_req_received(req);
		return status;
	}

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_SendToSam(struct netlogon_creds_cli_context *context,
				      struct dcerpc_binding_handle *b,
				      struct netr_SendToSamBase *message)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_NO_MEMORY;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_SendToSam_send(frame, ev, context, b, message);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_SendToSam_recv(req);
 fail:
	TALLOC_FREE(frame);
	return status;
}

struct netlogon_creds_cli_LogonGetDomainInfo_state {
	struct tevent_context *ev;
	struct netlogon_creds_cli_context *context;
	struct dcerpc_binding_handle *binding_handle;

	char *srv_name_slash;
	enum dcerpc_AuthType auth_type;
	enum dcerpc_AuthLevel auth_level;

	uint32_t level;
	union netr_WorkstationInfo *query;
	union netr_DomainInfo *info;

	struct netlogon_creds_CredentialState *creds;
	struct netlogon_creds_CredentialState tmp_creds;
	struct netr_Authenticator req_auth;
	struct netr_Authenticator rep_auth;
};

static void netlogon_creds_cli_LogonGetDomainInfo_cleanup(struct tevent_req *req,
						     NTSTATUS status);
static void netlogon_creds_cli_LogonGetDomainInfo_locked(struct tevent_req *subreq);

struct tevent_req *netlogon_creds_cli_LogonGetDomainInfo_send(TALLOC_CTX *mem_ctx,
					struct tevent_context *ev,
					struct netlogon_creds_cli_context *context,
					struct dcerpc_binding_handle *b,
					uint32_t level,
					union netr_WorkstationInfo *query)
{
	struct tevent_req *req;
	struct netlogon_creds_cli_LogonGetDomainInfo_state *state;
	struct tevent_req *subreq;

	req = tevent_req_create(mem_ctx, &state,
				struct netlogon_creds_cli_LogonGetDomainInfo_state);
	if (req == NULL) {
		return NULL;
	}

	state->ev = ev;
	state->context = context;
	state->binding_handle = b;

	state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
						context->server.computer);
	if (tevent_req_nomem(state->srv_name_slash, req)) {
		return tevent_req_post(req, ev);
	}

	state->level = level;
	state->query = query;
	state->info = talloc_zero(state, union netr_DomainInfo);
	if (tevent_req_nomem(state->info, req)) {
		return tevent_req_post(req, ev);
	}

	dcerpc_binding_handle_auth_info(state->binding_handle,
					&state->auth_type,
					&state->auth_level);

	subreq = netlogon_creds_cli_lock_send(state, state->ev,
					      state->context);
	if (tevent_req_nomem(subreq, req)) {
		return tevent_req_post(req, ev);
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_LogonGetDomainInfo_locked,
				req);

	return req;
}

static void netlogon_creds_cli_LogonGetDomainInfo_cleanup(struct tevent_req *req,
							 NTSTATUS status)
{
	struct netlogon_creds_cli_LogonGetDomainInfo_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_LogonGetDomainInfo_state);

	if (state->creds == NULL) {
		return;
	}

	if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
	    !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
		TALLOC_FREE(state->creds);
		return;
	}

	netlogon_creds_cli_delete(state->context, state->creds);
}

static void netlogon_creds_cli_LogonGetDomainInfo_done(struct tevent_req *subreq);

static void netlogon_creds_cli_LogonGetDomainInfo_locked(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_LogonGetDomainInfo_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_LogonGetDomainInfo_state);
	NTSTATUS status;

	status = netlogon_creds_cli_lock_recv(subreq, state,
					      &state->creds);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	status = netlogon_creds_cli_check_transport(state->auth_type,
						    state->auth_level,
						    state->creds,
						    DCERPC_AUTH_LEVEL_NONE);
	if (tevent_req_nterror(req, status)) {
		return;
	}

	/*
	 * we defer all callbacks in order to cleanup
	 * the database record.
	 */
	tevent_req_defer_callback(req, state->ev);

	state->tmp_creds = *state->creds;
	status = netlogon_creds_client_authenticator(&state->tmp_creds,
						     &state->req_auth);
	if (tevent_req_nterror(req, status)) {
		return;
	}
	ZERO_STRUCT(state->rep_auth);

	subreq = dcerpc_netr_LogonGetDomainInfo_send(state, state->ev,
						state->binding_handle,
						state->srv_name_slash,
						state->tmp_creds.computer_name,
						&state->req_auth,
						&state->rep_auth,
						state->level,
						state->query,
						state->info);
	if (tevent_req_nomem(subreq, req)) {
		status = NT_STATUS_NO_MEMORY;
		netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
		return;
	}

	tevent_req_set_callback(subreq,
				netlogon_creds_cli_LogonGetDomainInfo_done,
				req);
}

static void netlogon_creds_cli_LogonGetDomainInfo_done(struct tevent_req *subreq)
{
	struct tevent_req *req =
		tevent_req_callback_data(subreq,
		struct tevent_req);
	struct netlogon_creds_cli_LogonGetDomainInfo_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_LogonGetDomainInfo_state);
	NTSTATUS status;
	NTSTATUS result;

	/*
	 * We use state->dns_names as the memory context, as this is
	 * the only in/out variable and it has been overwritten by the
	 * out parameter from the server.
	 *
	 * We need to preserve the return value until the caller can use it.
	 */
	status = dcerpc_netr_LogonGetDomainInfo_recv(subreq, state->info, &result);
	TALLOC_FREE(subreq);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
		return;
	}

	status = netlogon_creds_client_verify(&state->tmp_creds,
					      &state->rep_auth.cred,
					      state->auth_type,
					      state->auth_level);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
		return;
	}

	if (tevent_req_nterror(req, result)) {
		netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, result);
		return;
	}

	*state->creds = state->tmp_creds;
	status = netlogon_creds_cli_store(state->context,
					  state->creds);
	if (tevent_req_nterror(req, status)) {
		netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
		return;
	}

	tevent_req_done(req);
}

NTSTATUS netlogon_creds_cli_LogonGetDomainInfo_recv(struct tevent_req *req,
			TALLOC_CTX *mem_ctx,
			union netr_DomainInfo **info)
{
	struct netlogon_creds_cli_LogonGetDomainInfo_state *state =
		tevent_req_data(req,
		struct netlogon_creds_cli_LogonGetDomainInfo_state);
	NTSTATUS status;

	if (tevent_req_is_nterror(req, &status)) {
		netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
		tevent_req_received(req);
		return status;
	}

	*info = talloc_move(mem_ctx, &state->info);

	tevent_req_received(req);
	return NT_STATUS_OK;
}

NTSTATUS netlogon_creds_cli_LogonGetDomainInfo(
			struct netlogon_creds_cli_context *context,
			struct dcerpc_binding_handle *b,
			TALLOC_CTX *mem_ctx,
			uint32_t level,
			union netr_WorkstationInfo *query,
			union netr_DomainInfo **info)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	NTSTATUS status = NT_STATUS_OK;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		goto fail;
	}
	req = netlogon_creds_cli_LogonGetDomainInfo_send(frame, ev, context, b,
							 level, query);
	if (req == NULL) {
		goto fail;
	}
	if (!tevent_req_poll_ntstatus(req, ev, &status)) {
		goto fail;
	}
	status = netlogon_creds_cli_LogonGetDomainInfo_recv(req,
							    mem_ctx,
							    info);
 fail:
	TALLOC_FREE(frame);
	return status;
}
