libcli/auth: add netlogon_creds_cli* infrastructure
authorStefan Metzmacher <metze@samba.org>
Thu, 18 Apr 2013 17:16:42 +0000 (19:16 +0200)
committerStefan Metzmacher <metze@samba.org>
Tue, 17 Dec 2013 20:10:16 +0000 (21:10 +0100)
This provides an abstraction to hide netlogon_creds_CredentialState,
which is stored in a node local tdb.

Where the global state (netlogon_creds_CredentialState) between client and
server was only kept in memory (on the client side), we now use
the abstracted netlogon_creds_cli_context.

We now use a node specific computer name in order to establish
individual netlogon sessions per node.

If the caller wants to use some netlogon calls with credential chain
(struct netr_Authenticator), netlogon_creds_cli_lock*() is used
to get the current netlogon_creds_CredentialState in a g_lock'ed
fashion, a talloc_free() will release the lock.

The locking is needed as there might be more than one process
(multiple winbindd child, cmdline tools) which want to talk
to a specific domain controller. The usage of netlogon_creds_CredentialState
needs to be serialized as it uses sequence numbers.

LogonSamLogonEx doesn't use the credential chain, but for some operations
it needs the global session in order to de/encrypt individual fields.
It uses the lockless netlogon_creds_cli_get() and netlogon_creds_cli_validate()
functions, which just make sure the session hasn't changed between
get and validate.

This is prepares the proper fix for a large number of bugs:
https://bugzilla.samba.org/show_bug.cgi?id=6563
https://bugzilla.samba.org/show_bug.cgi?id=7944
https://bugzilla.samba.org/show_bug.cgi?id=7945
https://bugzilla.samba.org/show_bug.cgi?id=7568
https://bugzilla.samba.org/show_bug.cgi?id=8599

Signed-off-by: Stefan Metzmacher <metze@samba.org>
libcli/auth/netlogon_creds_cli.c [new file with mode: 0644]
libcli/auth/netlogon_creds_cli.h [new file with mode: 0644]
libcli/auth/wscript_build

diff --git a/libcli/auth/netlogon_creds_cli.c b/libcli/auth/netlogon_creds_cli.c
new file mode 100644 (file)
index 0000000..dbfd62a
--- /dev/null
@@ -0,0 +1,2501 @@
+/*
+   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/server_id.h"
+#include "netlogon_creds_cli.h"
+#include "source3/include/messages.h"
+#include "source3/include/g_lock.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;
+               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;
+       } 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,
+                             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,
+                               TALLOC_CTX *mem_ctx,
+                               struct netlogon_creds_cli_context **_context)
+{
+       struct netlogon_creds_cli_context *context = 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->db.key_name = talloc_asprintf(context, "CLI[%s/%s]/SRV[%s/%s]",
+                                            client_computer,
+                                            client_account,
+                                            server_computer,
+                                            server_netbios_domain);
+       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_open_global_db(struct loadparm_context *lp_ctx)
+{
+       char *fname;
+       struct db_context *global_db;
+
+       if (netlogon_creds_cli_global_db != NULL) {
+               return NT_STATUS_OK;
+       }
+
+       fname = lpcfg_private_db_path(talloc_autofree_context(), lp_ctx, "netlogon_creds_cli");
+       if (fname == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       global_db = dbwrap_local_open(talloc_autofree_context(), lp_ctx,
+                                     fname, 0,
+                                     TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH,
+                                     O_RDWR|O_CREAT,
+                                     0600, DBWRAP_LOCK_ORDER_2);
+       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;
+}
+
+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,
+                               TALLOC_CTX *mem_ctx,
+                               struct netlogon_creds_cli_context **_context)
+{
+       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 = false;
+       bool require_strong_key = false;
+       int require_sign_or_seal = true;
+       bool seal_secure_channel = true;
+       enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE;
+       bool disable_aes_schannel = false;
+       bool neutralize_nt4_emulation = false;
+       struct server_id self = {
+               .vnn = NONCLUSTER_VNN,
+               .unique_id = SERVERID_UNIQUE_ID_NOT_TO_VERIFY,
+       };
+
+       if (msg_ctx != NULL) {
+               self = messaging_server_id(msg_ctx);
+       }
+
+       *_context = NULL;
+
+       if (self.vnn != NONCLUSTER_VNN) {
+               client_computer = talloc_asprintf(mem_ctx,
+                                                 "%s_cluster_vnn_%u",
+                                                 lpcfg_netbios_name(lp_ctx),
+                                                 (unsigned)self.vnn);
+               if (client_computer == NULL) {
+                       return NT_STATUS_NO_MEMORY;
+               }
+       } else {
+               client_computer = lpcfg_netbios_name(lp_ctx);
+       }
+
+       /*
+        * allow overwrite per domain
+        * reject md5 servers:<netbios_domain>
+        */
+       //TODO: add lpcfp_reject_md5_servers()
+       reject_md5_servers = lpcfg_parm_bool(lp_ctx, NULL,
+                                            "__default__",
+                                            "reject md5 servers",
+                                            reject_md5_servers);
+       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>
+        */
+       //TODO: add lpcfp_require_strong_key()
+       require_strong_key = lpcfg_parm_bool(lp_ctx, NULL,
+                                            "__default__",
+                                            "require strong key",
+                                            require_strong_key);
+       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
+        * disable aes schannel:<netbios_domain>
+        */
+       //TODO: add lpcfp_disable_aes_schannel()
+       disable_aes_schannel = lpcfg_parm_bool(lp_ctx, NULL,
+                                              "__default__",
+                                              "disable aes schannel",
+                                              disable_aes_schannel);
+       disable_aes_schannel = lpcfg_parm_bool(lp_ctx, NULL,
+                                              "disable aes schannel",
+                                              server_netbios_domain,
+                                              disable_aes_schannel);
+
+       /*
+        * allow overwrite per domain
+        * neutralize nt4 emulation:<netbios_domain>
+        */
+       //TODO: add lpcfp_neutralize_nt4_emulation()
+       neutralize_nt4_emulation = lpcfg_parm_bool(lp_ctx, NULL,
+                                                  "__default__",
+                                                  "neutralize nt4 emulation",
+                                                  neutralize_nt4_emulation);
+       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;
+       if (!disable_aes_schannel) {
+               proposed_flags |= NETLOGON_NEG_SUPPORTS_AES;
+       }
+       if (neutralize_nt4_emulation) {
+               proposed_flags |= NETLOGON_NEG_NEUTRALIZE_NT4_EMULATION;
+       }
+
+       if (require_sign_or_seal == true) {
+               required_flags |= NETLOGON_NEG_ARCFOUR;
+               required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC;
+       } else if (require_sign_or_seal == false) {
+               proposed_flags &= ~NETLOGON_NEG_AUTHENTICATED_RPC;
+       } else {
+               required_flags |= NETLOGON_NEG_ARCFOUR;
+       }
+
+       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_PASSWORD_SET2;
+               required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC;
+       }
+
+       proposed_flags |= required_flags;
+
+       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)) {
+               return status;
+       }
+
+       if (msg_ctx != NULL) {
+               context->db.g_ctx = g_lock_ctx_init(context, msg_ctx);
+               if (context->db.g_ctx == NULL) {
+                       TALLOC_FREE(context);
+                       return NT_STATUS_NO_MEMORY;
+               }
+       }
+
+       if (netlogon_creds_cli_global_db != NULL) {
+               context->db.ctx = netlogon_creds_cli_global_db;
+               *_context = context;
+               return NT_STATUS_OK;
+       }
+
+       status = netlogon_creds_cli_open_global_db(lp_ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               talloc_free(context);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       context->db.ctx = netlogon_creds_cli_global_db;
+       *_context = context;
+       return NT_STATUS_OK;
+}
+
+NTSTATUS netlogon_creds_cli_context_tmp(const char *client_computer,
+                               const char *client_account,
+                               enum netr_SchannelType type,
+                               uint32_t proposed_flags,
+                               uint32_t required_flags,
+                               enum dcerpc_AuthLevel auth_level,
+                               const char *server_computer,
+                               const char *server_netbios_domain,
+                               TALLOC_CTX *mem_ctx,
+                               struct netlogon_creds_cli_context **_context)
+{
+       NTSTATUS status;
+       struct netlogon_creds_cli_context *context = NULL;
+
+       *_context = NULL;
+
+       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)) {
+               return status;
+       }
+
+       context->db.ctx = db_open_rbt(context);
+       if (context->db.ctx == NULL) {
+               talloc_free(context);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       *_context = context;
+       return NT_STATUS_OK;
+}
+
+NTSTATUS netlogon_creds_cli_context_copy(
+                               const struct netlogon_creds_cli_context *src,
+                               TALLOC_CTX *mem_ctx,
+                               struct netlogon_creds_cli_context **_dst)
+{
+       struct netlogon_creds_cli_context *dst;
+
+       dst = talloc_zero(mem_ctx, struct netlogon_creds_cli_context);
+       if (dst == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       *dst = *src;
+
+       dst->client.computer = talloc_strdup(dst, src->client.computer);
+       if (dst->client.computer == NULL) {
+               TALLOC_FREE(dst);
+               return NT_STATUS_NO_MEMORY;
+       }
+       dst->client.account = talloc_strdup(dst, src->client.account);
+       if (dst->client.account == NULL) {
+               TALLOC_FREE(dst);
+               return NT_STATUS_NO_MEMORY;
+       }
+       dst->server.computer = talloc_strdup(dst, src->server.computer);
+       if (dst->server.computer == NULL) {
+               TALLOC_FREE(dst);
+               return NT_STATUS_NO_MEMORY;
+       }
+       dst->server.netbios_domain = talloc_strdup(dst, src->server.netbios_domain);
+       if (dst->server.netbios_domain == NULL) {
+               TALLOC_FREE(dst);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       dst->db.key_name = talloc_strdup(dst, src->db.key_name);
+       if (dst->db.key_name == NULL) {
+               TALLOC_FREE(dst);
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       dst->db.key_data = string_term_tdb_data(dst->db.key_name);
+
+       *_dst = dst;
+       return NT_STATUS_OK;
+}
+
+enum dcerpc_AuthLevel netlogon_creds_cli_auth_level(
+               struct netlogon_creds_cli_context *context)
+{
+       return context->client.auth_level;
+}
+
+struct netlogon_creds_cli_fetch_state {
+       TALLOC_CTX *mem_ctx;
+       struct netlogon_creds_CredentialState *creds;
+       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;
+       uint32_t tmp_flags;
+
+       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;
+       }
+
+       tmp_flags = state->creds->negotiate_flags;
+       tmp_flags &= state->required_flags;
+       if (tmp_flags != state->required_flags) {
+               TALLOC_FREE(state->creds);
+               state->status = NT_STATUS_DOWNGRADE_DETECTED;
+               return;
+       }
+
+       state->status = NT_STATUS_OK;
+}
+
+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_cli_fetch_state fstate = {
+               .mem_ctx = mem_ctx,
+               .status = NT_STATUS_INTERNAL_ERROR,
+               .required_flags = context->client.required_flags,
+       };
+       static const struct netr_Credential zero_creds;
+
+       *_creds = NULL;
+
+       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;
+       }
+       status = fstate.status;
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       /*
+        * mark it as invalid for step operations.
+        */
+       fstate.creds->sequence = 0;
+       fstate.creds->seed = zero_creds;
+       fstate.creds->client = zero_creds;
+       fstate.creds->server = zero_creds;
+
+       if (context->server.cached_flags == fstate.creds->negotiate_flags) {
+               *_creds = 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, while the client is free to use
+        * any computer name. We include the cluster node number
+        * in our computer name in order to avoid cross node
+        * coordination of the credential chain.
+        *
+        * 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;
+       }
+
+       *_creds = fstate.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;
+       int cmp;
+
+       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;
+       }
+
+       if (blob1.length != blob2.length) {
+               TALLOC_FREE(frame);
+               return false;
+       }
+
+       cmp = memcmp(blob1.data, blob2.data, blob1.length);
+       if (cmp != 0) {
+               TALLOC_FREE(frame);
+               return false;
+       }
+
+       TALLOC_FREE(frame);
+       return true;
+}
+
+NTSTATUS netlogon_creds_cli_store(struct netlogon_creds_cli_context *context,
+                                 struct netlogon_creds_CredentialState **_creds)
+{
+       struct netlogon_creds_CredentialState *creds = *_creds;
+       NTSTATUS status;
+       enum ndr_err_code ndr_err;
+       DATA_BLOB blob;
+       TDB_DATA data;
+
+       *_creds = NULL;
+
+       if (context->db.locked_state == NULL) {
+               /*
+                * this was not the result of netlogon_creds_cli_lock*()
+                */
+               TALLOC_FREE(creds);
+               return NT_STATUS_INVALID_PAGE_PROTECTION;
+       }
+
+       if (context->db.locked_state->creds != creds) {
+               /*
+                * this was not the result of netlogon_creds_cli_lock*()
+                */
+               TALLOC_FREE(creds);
+               return NT_STATUS_INVALID_PAGE_PROTECTION;
+       }
+
+       ndr_err = ndr_push_struct_blob(&blob, creds, creds,
+               (ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState);
+       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+               TALLOC_FREE(creds);
+               status = ndr_map_error2ntstatus(ndr_err);
+               return status;
+       }
+
+       data.dptr = blob.data;
+       data.dsize = blob.length;
+
+       status = dbwrap_store(context->db.ctx,
+                             context->db.key_data,
+                             data, TDB_REPLACE);
+       TALLOC_FREE(creds);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       return NT_STATUS_OK;
+}
+
+NTSTATUS netlogon_creds_cli_delete(struct netlogon_creds_cli_context *context,
+                                  struct netlogon_creds_CredentialState **_creds)
+{
+       struct netlogon_creds_CredentialState *creds = *_creds;
+       NTSTATUS status;
+
+       *_creds = NULL;
+
+       if (context->db.locked_state == NULL) {
+               /*
+                * this was not the result of netlogon_creds_cli_lock*()
+                */
+               TALLOC_FREE(creds);
+               return NT_STATUS_INVALID_PAGE_PROTECTION;
+       }
+
+       if (context->db.locked_state->creds != creds) {
+               /*
+                * this was not the result of netlogon_creds_cli_lock*()
+                */
+               TALLOC_FREE(creds);
+               return NT_STATUS_INVALID_PAGE_PROTECTION;
+       }
+
+       status = dbwrap_delete(context->db.ctx,
+                              context->db.key_data);
+       TALLOC_FREE(creds);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       return NT_STATUS_OK;
+}
+
+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);
+static void netlogon_creds_cli_lock_fetch(struct tevent_req *req);
+
+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) {
+               netlogon_creds_cli_lock_fetch(req);
+               if (!tevent_req_is_in_progress(req)) {
+                       return tevent_req_post(req, ev);
+               }
+
+               return req;
+       }
+
+       subreq = g_lock_lock_send(state, ev,
+                                 context->db.g_ctx,
+                                 context->db.key_name,
+                                 G_LOCK_WRITE);
+       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;
+
+       netlogon_creds_cli_lock_fetch(req);
+}
+
+static void netlogon_creds_cli_lock_fetch(struct tevent_req *req)
+{
+       struct netlogon_creds_cli_lock_state *state =
+               tevent_req_data(req,
+               struct netlogon_creds_cli_lock_state);
+       struct netlogon_creds_cli_context *context = state->locked_state->context;
+       struct netlogon_creds_cli_fetch_state fstate = {
+               .status = NT_STATUS_INTERNAL_ERROR,
+               .required_flags = context->client.required_flags,
+       };
+       NTSTATUS status;
+
+       fstate.mem_ctx = state;
+       status = dbwrap_parse_record(context->db.ctx,
+                                    context->db.key_data,
+                                    netlogon_creds_cli_fetch_parser,
+                                    &fstate);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       status = fstate.status;
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       if (context->server.cached_flags == fstate.creds->negotiate_flags) {
+               state->creds = fstate.creds;
+               tevent_req_done(req);
+               return;
+       }
+
+       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;
+       }
+
+       state->creds = fstate.creds;
+       tevent_req_done(req);
+       return;
+}
+
+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_auth_state {
+       struct tevent_context *ev;
+       struct netlogon_creds_cli_context *context;
+       struct dcerpc_binding_handle *binding_handle;
+       struct samr_Password current_nt_hash;
+       struct samr_Password previous_nt_hash;
+       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 rid;
+       bool try_auth3;
+       bool try_auth2;
+       bool require_auth2;
+       bool try_previous_nt_hash;
+       struct netlogon_creds_cli_locked_state *locked_state;
+};
+
+static void netlogon_creds_cli_auth_locked(struct tevent_req *subreq);
+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,
+                               struct samr_Password current_nt_hash,
+                               const struct samr_Password *previous_nt_hash)
+{
+       struct tevent_req *req;
+       struct netlogon_creds_cli_auth_state *state;
+       struct netlogon_creds_cli_locked_state *locked_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;
+       state->current_nt_hash = current_nt_hash;
+       if (previous_nt_hash != NULL) {
+               state->previous_nt_hash = *previous_nt_hash;
+               state->try_previous_nt_hash = true;
+       }
+
+       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;
+
+       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);
+       }
+
+       if (context->client.required_flags & NETLOGON_NEG_SUPPORTS_AES) {
+               state->try_auth3 = true;
+               state->try_auth2 = true;
+               state->require_auth2 = true;
+       }
+
+       if (context->client.required_flags & NETLOGON_NEG_STRONG_KEYS) {
+               state->try_auth2 = true;
+               state->require_auth2 = true;
+       }
+
+       if (context->client.proposed_flags & NETLOGON_NEG_AUTHENTICATED_RPC) {
+               state->try_auth3 = true;
+               state->try_auth2 = true;
+       }
+
+       state->used_nt_hash = state->current_nt_hash;
+       state->current_flags = context->client.proposed_flags;
+
+       if (context->db.g_ctx != NULL) {
+               struct tevent_req *subreq;
+
+               subreq = g_lock_lock_send(state, ev,
+                                         context->db.g_ctx,
+                                         context->db.key_name,
+                                         G_LOCK_WRITE);
+               if (tevent_req_nomem(subreq, req)) {
+                       return tevent_req_post(req, ev);
+               }
+               tevent_req_set_callback(subreq,
+                                       netlogon_creds_cli_auth_locked,
+                                       req);
+
+               return req;
+       }
+
+       status = dbwrap_delete(state->context->db.ctx,
+                              state->context->db.key_data);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+               status = NT_STATUS_OK;
+       }
+       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_locked(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;
+
+       status = g_lock_lock_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+       state->locked_state->is_glocked = true;
+
+       status = dbwrap_delete(state->context->db.ctx,
+                              state->context->db.key_data);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+               status = NT_STATUS_OK;
+       }
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       netlogon_creds_cli_auth_challenge_start(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);
+
+       generate_random_buffer(state->client_challenge.data,
+                              sizeof(state->client_challenge.data));
+
+       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;
+       }
+
+       /* 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->current_flags);
+       if (tevent_req_nomem(state->creds, req)) {
+               return;
+       }
+
+       if (state->try_auth3) {
+               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->creds->negotiate_flags,
+                                               &state->rid);
+               if (tevent_req_nomem(subreq, req)) {
+                       return;
+               }
+       } else if (state->try_auth2) {
+               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->creds->negotiate_flags);
+               if (tevent_req_nomem(subreq, req)) {
+                       return;
+               }
+       } else {
+               state->rid = 0;
+               /*
+                * keep old samba versions happy.
+                */
+               if (state->current_flags & NETLOGON_NEG_AUTHENTICATED_RPC) {
+                       state->creds->negotiate_flags = NETLOGON_NEG_AUTHENTICATED_RPC;
+               }
+
+               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 ok;
+       enum ndr_err_code ndr_err;
+       DATA_BLOB blob;
+       TDB_DATA data;
+       uint32_t tmp_flags;
+
+       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;
+       }
+
+       tmp_flags = state->creds->negotiate_flags;
+       tmp_flags &= state->context->client.required_flags;
+       if (tmp_flags != state->context->client.required_flags) {
+               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)) {
+
+               tmp_flags = state->context->client.proposed_flags;
+               if ((state->current_flags == tmp_flags) &&
+                   (state->creds->negotiate_flags != tmp_flags))
+               {
+                       /*
+                        * lets retry with the negotiated flags
+                        */
+                       state->current_flags = state->creds->negotiate_flags;
+                       netlogon_creds_cli_auth_challenge_start(req);
+                       return;
+               }
+
+               if (!state->try_previous_nt_hash) {
+                       /*
+                        * we already retried, giving up...
+                        */
+                       tevent_req_nterror(req, result);
+                       return;
+               }
+
+               /*
+                * lets retry with the old nt hash.
+                */
+               state->try_previous_nt_hash = false;
+               state->used_nt_hash = state->previous_nt_hash;
+               state->current_flags = state->context->client.proposed_flags;
+               netlogon_creds_cli_auth_challenge_start(req);
+               return;
+       }
+
+       ok = netlogon_creds_client_check(state->creds,
+                                        &state->server_credential);
+       if (!ok) {
+               tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+               return;
+       }
+
+       ndr_err = ndr_push_struct_blob(&blob, state, state->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);
+               tevent_req_nterror(req, status);
+               return;
+       }
+
+       data.dptr = blob.data;
+       data.dsize = blob.length;
+
+       status = dbwrap_store(state->context->db.ctx,
+                             state->context->db.key_data,
+                             data, TDB_REPLACE);
+       TALLOC_FREE(state->locked_state);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       tevent_req_done(req);
+}
+
+NTSTATUS netlogon_creds_cli_auth_recv(struct tevent_req *req)
+{
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               tevent_req_received(req);
+               return status;
+       }
+
+       tevent_req_received(req);
+       return NT_STATUS_OK;
+}
+
+NTSTATUS netlogon_creds_cli_auth(struct netlogon_creds_cli_context *context,
+                                struct dcerpc_binding_handle *b,
+                                struct samr_Password current_nt_hash,
+                                const struct samr_Password *previous_nt_hash)
+{
+       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_auth_send(frame, ev, context, b,
+                                          current_nt_hash,
+                                          previous_nt_hash);
+       if (req == NULL) {
+               goto fail;
+       }
+       if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+               goto fail;
+       }
+       status = netlogon_creds_cli_auth_recv(req);
+ 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;
+
+       char *srv_name_slash;
+
+       union netr_Capabilities caps;
+
+       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_check_cleanup(struct tevent_req *req,
+                                                    NTSTATUS status);
+static void netlogon_creds_cli_check_locked(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;
+       enum dcerpc_AuthType auth_type;
+       enum dcerpc_AuthLevel auth_level;
+
+       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;
+
+       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,
+                                       &auth_type, &auth_level);
+
+       if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) {
+               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+               return tevent_req_post(req, ev);
+       }
+
+       switch (auth_level) {
+       case DCERPC_AUTH_LEVEL_INTEGRITY:
+       case DCERPC_AUTH_LEVEL_PRIVACY:
+               break;
+       default:
+               tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+               return tevent_req_post(req, ev);
+       }
+
+       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_check_locked,
+                               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(state->context, &state->creds);
+}
+
+static void netlogon_creds_cli_check_caps(struct tevent_req *subreq);
+
+static void netlogon_creds_cli_check_locked(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;
+
+       status = netlogon_creds_cli_lock_recv(subreq, state,
+                                             &state->creds);
+       TALLOC_FREE(subreq);
+       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;
+       netlogon_creds_client_authenticator(&state->tmp_creds,
+                                           &state->req_auth);
+       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)) {
+               status = NT_STATUS_NO_MEMORY;
+               netlogon_creds_cli_check_cleanup(req, status);
+               return;
+       }
+       tevent_req_set_callback(subreq,
+                               netlogon_creds_cli_check_caps,
+                               req);
+}
+
+static void netlogon_creds_cli_check_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;
+       bool ok;
+
+       status = dcerpc_netr_LogonGetCapabilities_recv(subreq, state,
+                                                      &result);
+       TALLOC_FREE(subreq);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+               uint32_t tmp = state->context->client.proposed_flags;
+
+               if (tmp & NETLOGON_NEG_SUPPORTS_AES) {
+                       /*
+                        * Old Samba servers need
+                        * "disable aes schannel = yes"
+                        */
+                       status = NT_STATUS_DOWNGRADE_DETECTED;
+                       tevent_req_nterror(req, status);
+                       netlogon_creds_cli_check_cleanup(req, status);
+                       return;
+               }
+
+               /*
+                * If we have not proposed NETLOGON_NEG_SUPPORTS_AES
+                * ("disable aes schannel = yes")
+                * it's ok to ignore NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE.
+                *
+                * This is needed against old Samba servers.
+                */
+               netlogon_creds_cli_check_cleanup(req, result);
+               tevent_req_done(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)) {
+               /*
+                * 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;
+       }
+
+       ok = netlogon_creds_client_check(&state->tmp_creds,
+                                        &state->rep_auth.cred);
+       if (!ok) {
+               status = NT_STATUS_ACCESS_DENIED;
+               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->tmp_creds.negotiate_flags) {
+               status = NT_STATUS_DOWNGRADE_DETECTED;
+               tevent_req_nterror(req, status);
+               netlogon_creds_cli_check_cleanup(req, status);
+               return;
+       }
+
+       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;
+       }
+
+       *state->creds = state->tmp_creds;
+       status = netlogon_creds_cli_store(state->context,
+                                         &state->creds);
+       netlogon_creds_cli_check_cleanup(req, status);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       tevent_req_done(req);
+}
+
+NTSTATUS netlogon_creds_cli_check_recv(struct tevent_req *req)
+{
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               netlogon_creds_cli_check_cleanup(req, status);
+               tevent_req_received(req);
+               return status;
+       }
+
+       tevent_req_received(req);
+       return NT_STATUS_OK;
+}
+
+NTSTATUS netlogon_creds_cli_check(struct netlogon_creds_cli_context *context,
+                                 struct dcerpc_binding_handle *b)
+{
+       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);
+ 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 char *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;
+
+       /*
+        * netr_ServerPasswordSet
+        */
+       E_md4hash(new_password, state->samr_password.hash);
+
+       /*
+        * netr_ServerPasswordSet2
+        */
+       ok = encode_pw_buffer(state->samr_crypt_password.data,
+                             new_password, STR_UNICODE);
+       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 (ofs < 12) {
+                       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);
+}
+
+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;
+       }
+
+       if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) {
+               switch (state->auth_level) {
+               case DCERPC_AUTH_LEVEL_INTEGRITY:
+               case DCERPC_AUTH_LEVEL_PRIVACY:
+                       break;
+               default:
+                       tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+                       return;
+               }
+       } else {
+               uint32_t tmp = state->creds->negotiate_flags;
+
+               if (tmp & 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.
+                        */
+                       tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+                       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;
+       netlogon_creds_client_authenticator(&state->tmp_creds,
+                                           &state->req_auth);
+       ZERO_STRUCT(state->rep_auth);
+
+       if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_PASSWORD_SET2) {
+
+               if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
+                       netlogon_creds_aes_encrypt(&state->tmp_creds,
+                                       state->samr_crypt_password.data,
+                                       516);
+               } else {
+                       netlogon_creds_arcfour_crypt(&state->tmp_creds,
+                                       state->samr_crypt_password.data,
+                                       516);
+               }
+
+               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 {
+               netlogon_creds_des_encrypt(&state->tmp_creds,
+                                          &state->samr_password);
+
+               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;
+       bool ok;
+
+       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;
+               }
+       }
+
+       ok = netlogon_creds_client_check(&state->tmp_creds,
+                                        &state->rep_auth.cred);
+       if (!ok) {
+               status = NT_STATUS_ACCESS_DENIED;
+               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);
+       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 char *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
+        */
+       struct netlogon_creds_CredentialState *ro_creds;
+
+       struct netlogon_creds_CredentialState *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_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);
+}
+
+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;
+                       }
+
+                       netlogon_creds_encrypt_samlogon_logon(state->ro_creds,
+                                                             state->logon_level,
+                                                             state->logon);
+               }
+
+               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;
+       netlogon_creds_client_authenticator(&state->tmp_creds,
+                                           &state->req_auth);
+       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;
+       }
+
+       netlogon_creds_encrypt_samlogon_logon(state->ro_creds,
+                                             state->logon_level,
+                                             state->logon);
+
+       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);
+       NTSTATUS status;
+       NTSTATUS result;
+       bool ok;
+
+       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.
+                        */
+                       TALLOC_FREE(state->ro_creds);
+                       state->try_logon_ex = false;
+                       netlogon_creds_cli_LogonSamLogon_start(req);
+                       return;
+               }
+
+               netlogon_creds_decrypt_samlogon_validation(state->ro_creds,
+                                                       state->validation_level,
+                                                       state->validation);
+
+               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;
+               }
+       }
+
+       ok = netlogon_creds_client_check(&state->tmp_creds,
+                                        &state->rep_auth.cred);
+       if (!ok) {
+               status = NT_STATUS_ACCESS_DENIED;
+               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);
+       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;
+       }
+
+       netlogon_creds_decrypt_samlogon_validation(&state->tmp_creds,
+                                               state->validation_level,
+                                               state->validation);
+
+       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;
+}
diff --git a/libcli/auth/netlogon_creds_cli.h b/libcli/auth/netlogon_creds_cli.h
new file mode 100644 (file)
index 0000000..f8f2bef
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+   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/>.
+*/
+
+#ifndef NETLOGON_CREDS_CLI_H
+#define NETLOGON_CREDS_CLI_H
+
+#include "librpc/gen_ndr/dcerpc.h"
+#include "librpc/gen_ndr/schannel.h"
+
+struct netlogon_creds_cli_context;
+struct messaging_context;
+struct dcerpc_binding_handle;
+
+NTSTATUS netlogon_creds_cli_open_global_db(struct loadparm_context *lp_ctx);
+
+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,
+                               TALLOC_CTX *mem_ctx,
+                               struct netlogon_creds_cli_context **_context);
+NTSTATUS netlogon_creds_cli_context_tmp(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,
+                               TALLOC_CTX *mem_ctx,
+                               struct netlogon_creds_cli_context **_context);
+NTSTATUS netlogon_creds_cli_context_copy(
+                               const struct netlogon_creds_cli_context *src,
+                               TALLOC_CTX *mem_ctx,
+                               struct netlogon_creds_cli_context **_dst);
+
+enum dcerpc_AuthLevel netlogon_creds_cli_auth_level(
+               struct netlogon_creds_cli_context *context);
+
+NTSTATUS netlogon_creds_cli_get(struct netlogon_creds_cli_context *context,
+                               TALLOC_CTX *mem_ctx,
+                               struct netlogon_creds_CredentialState **_creds);
+bool netlogon_creds_cli_validate(struct netlogon_creds_cli_context *context,
+                       const struct netlogon_creds_CredentialState *creds1);
+
+NTSTATUS netlogon_creds_cli_store(struct netlogon_creds_cli_context *context,
+                                 struct netlogon_creds_CredentialState **_creds);
+NTSTATUS netlogon_creds_cli_delete(struct netlogon_creds_cli_context *context,
+                                  struct netlogon_creds_CredentialState **_creds);
+
+struct tevent_req *netlogon_creds_cli_lock_send(TALLOC_CTX *mem_ctx,
+                               struct tevent_context *ev,
+                               struct netlogon_creds_cli_context *context);
+NTSTATUS netlogon_creds_cli_lock_recv(struct tevent_req *req,
+                       TALLOC_CTX *mem_ctx,
+                       struct netlogon_creds_CredentialState **creds);
+NTSTATUS netlogon_creds_cli_lock(struct netlogon_creds_cli_context *context,
+                       TALLOC_CTX *mem_ctx,
+                       struct netlogon_creds_CredentialState **creds);
+
+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,
+                               struct samr_Password current_nt_hash,
+                               const struct samr_Password *previous_nt_hash);
+NTSTATUS netlogon_creds_cli_auth_recv(struct tevent_req *req);
+NTSTATUS netlogon_creds_cli_auth(struct netlogon_creds_cli_context *context,
+                                struct dcerpc_binding_handle *b,
+                                struct samr_Password current_nt_hash,
+                                const struct samr_Password *previous_nt_hash);
+
+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);
+NTSTATUS netlogon_creds_cli_check_recv(struct tevent_req *req);
+NTSTATUS netlogon_creds_cli_check(struct netlogon_creds_cli_context *context,
+                                 struct dcerpc_binding_handle *b);
+
+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 char *new_password,
+                               const uint32_t *new_version);
+NTSTATUS netlogon_creds_cli_ServerPasswordSet_recv(struct tevent_req *req);
+NTSTATUS netlogon_creds_cli_ServerPasswordSet(
+                               struct netlogon_creds_cli_context *context,
+                               struct dcerpc_binding_handle *b,
+                               const char *new_password,
+                               const uint32_t *new_version);
+
+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);
+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);
+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);
+
+#endif /* NETLOGON_CREDS_CLI_H */
index df23058f6e74a3e80e1d0900a73e39fdd8023371..d1a69fadbf3e4683edea6a99f16222dfbdbbf135 100755 (executable)
@@ -28,6 +28,10 @@ bld.SAMBA_SUBSYSTEM('COMMON_SCHANNEL',
        deps='dbwrap util_tdb samba-hostconfig NDR_NETLOGON'
        )
 
+bld.SAMBA_SUBSYSTEM('NETLOGON_CREDS_CLI',
+        source='netlogon_creds_cli.c',
+        deps='dbwrap util_tdb tevent-util samba-hostconfig RPC_NDR_NETLOGON NDR_NETLOGON'
+        )
 
 bld.SAMBA_SUBSYSTEM('PAM_ERRORS',
        source='pam_errors.c',