/*
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 .
*/
#include "includes.h"
#include "system/filesys.h"
#include
#include "lib/util/tevent_ntstatus.h"
#include "lib/dbwrap/dbwrap.h"
#include "lib/dbwrap/dbwrap_rbt.h"
#include "lib/util/util_tdb.h"
#include "libcli/security/security.h"
#include "../lib/param/param.h"
#include "../libcli/auth/schannel.h"
#include "../librpc/gen_ndr/ndr_schannel.h"
#include "../librpc/gen_ndr/ndr_netlogon_c.h"
#include "../librpc/gen_ndr/ndr_netlogon.h"
#include "../librpc/gen_ndr/server_id.h"
#include "netlogon_creds_cli.h"
#include "source3/include/messages.h"
#include "source3/include/g_lock.h"
#include "libds/common/roles.h"
#include "lib/crypto/md4.h"
#include "auth/credentials/credentials.h"
#include "lib/param/loadparm.h"
struct netlogon_creds_cli_locked_state;
struct netlogon_creds_cli_context {
struct {
const char *computer;
const char *account;
uint32_t proposed_flags;
uint32_t required_flags;
enum netr_SchannelType type;
enum dcerpc_AuthLevel auth_level;
} client;
struct {
const char *computer;
const char *netbios_domain;
const char *dns_domain;
uint32_t cached_flags;
bool try_validation6;
bool try_logon_ex;
bool try_logon_with;
} server;
struct {
const char *key_name;
TDB_DATA key_data;
struct db_context *ctx;
struct g_lock_ctx *g_ctx;
struct netlogon_creds_cli_locked_state *locked_state;
enum netlogon_creds_cli_lck_type lock;
} db;
};
struct netlogon_creds_cli_locked_state {
struct netlogon_creds_cli_context *context;
bool is_glocked;
struct netlogon_creds_CredentialState *creds;
};
static int netlogon_creds_cli_locked_state_destructor(
struct netlogon_creds_cli_locked_state *state)
{
struct netlogon_creds_cli_context *context = state->context;
if (context == NULL) {
return 0;
}
if (context->db.locked_state == state) {
context->db.locked_state = NULL;
}
if (state->is_glocked) {
g_lock_unlock(context->db.g_ctx,
string_term_tdb_data(context->db.key_name));
}
return 0;
}
static NTSTATUS netlogon_creds_cli_context_common(
const char *client_computer,
const char *client_account,
enum netr_SchannelType type,
enum dcerpc_AuthLevel auth_level,
uint32_t proposed_flags,
uint32_t required_flags,
const char *server_computer,
const char *server_netbios_domain,
const char *server_dns_domain,
TALLOC_CTX *mem_ctx,
struct netlogon_creds_cli_context **_context)
{
struct netlogon_creds_cli_context *context = NULL;
char *_key_name = NULL;
size_t server_netbios_name_len;
char *p = NULL;
*_context = NULL;
context = talloc_zero(mem_ctx, struct netlogon_creds_cli_context);
if (context == NULL) {
return NT_STATUS_NO_MEMORY;
}
context->client.computer = talloc_strdup(context, client_computer);
if (context->client.computer == NULL) {
TALLOC_FREE(context);
return NT_STATUS_NO_MEMORY;
}
context->client.account = talloc_strdup(context, client_account);
if (context->client.account == NULL) {
TALLOC_FREE(context);
return NT_STATUS_NO_MEMORY;
}
context->client.proposed_flags = proposed_flags;
context->client.required_flags = required_flags;
context->client.type = type;
context->client.auth_level = auth_level;
context->server.computer = talloc_strdup(context, server_computer);
if (context->server.computer == NULL) {
TALLOC_FREE(context);
return NT_STATUS_NO_MEMORY;
}
context->server.netbios_domain = talloc_strdup(context, server_netbios_domain);
if (context->server.netbios_domain == NULL) {
TALLOC_FREE(context);
return NT_STATUS_NO_MEMORY;
}
context->server.dns_domain = talloc_strdup(context, server_dns_domain);
if (context->server.dns_domain == NULL) {
TALLOC_FREE(context);
return NT_STATUS_NO_MEMORY;
}
/*
* TODO:
* Force the callers to provide a unique
* value for server_computer and use this directly.
*
* For now we have to deal with
* "HOSTNAME" vs. "hostname.example.com".
*/
p = strchr(server_computer, '.');
if (p != NULL) {
server_netbios_name_len = p-server_computer;
} else {
server_netbios_name_len = strlen(server_computer);
}
_key_name = talloc_asprintf(context, "CLI[%s/%s]/SRV[%.*s/%s]",
client_computer,
client_account,
(int)server_netbios_name_len,
server_computer,
server_netbios_domain);
if (_key_name == NULL) {
TALLOC_FREE(context);
return NT_STATUS_NO_MEMORY;
}
context->db.key_name = talloc_strdup_upper(context, _key_name);
TALLOC_FREE(_key_name);
if (context->db.key_name == NULL) {
TALLOC_FREE(context);
return NT_STATUS_NO_MEMORY;
}
context->db.key_data = string_term_tdb_data(context->db.key_name);
*_context = context;
return NT_STATUS_OK;
}
static struct db_context *netlogon_creds_cli_global_db;
NTSTATUS netlogon_creds_cli_set_global_db(struct loadparm_context *lp_ctx,
struct db_context **db)
{
netlogon_creds_cli_warn_options(lp_ctx);
if (netlogon_creds_cli_global_db != NULL) {
return NT_STATUS_INVALID_PARAMETER_MIX;
}
netlogon_creds_cli_global_db = talloc_move(NULL, db);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_open_global_db(struct loadparm_context *lp_ctx)
{
char *fname;
struct db_context *global_db;
int hash_size, tdb_flags;
netlogon_creds_cli_warn_options(lp_ctx);
if (netlogon_creds_cli_global_db != NULL) {
return NT_STATUS_OK;
}
fname = lpcfg_private_db_path(NULL, lp_ctx, "netlogon_creds_cli");
if (fname == NULL) {
return NT_STATUS_NO_MEMORY;
}
hash_size = lpcfg_tdb_hash_size(lp_ctx, fname);
tdb_flags = lpcfg_tdb_flags(
lp_ctx,
TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH);
global_db = dbwrap_local_open(
NULL,
fname,
hash_size,
tdb_flags,
O_RDWR|O_CREAT,
0600,
DBWRAP_LOCK_ORDER_2,
DBWRAP_FLAG_NONE);
if (global_db == NULL) {
DEBUG(0,("netlogon_creds_cli_open_global_db: Failed to open %s - %s\n",
fname, strerror(errno)));
talloc_free(fname);
return NT_STATUS_NO_MEMORY;
}
TALLOC_FREE(fname);
netlogon_creds_cli_global_db = global_db;
return NT_STATUS_OK;
}
void netlogon_creds_cli_close_global_db(void)
{
TALLOC_FREE(netlogon_creds_cli_global_db);
}
void netlogon_creds_cli_warn_options(struct loadparm_context *lp_ctx)
{
bool global_reject_md5_servers = lpcfg_reject_md5_servers(lp_ctx);
bool global_require_strong_key = lpcfg_require_strong_key(lp_ctx);
int global_client_schannel = lpcfg_client_schannel(lp_ctx);
bool global_seal_secure_channel = lpcfg_winbind_sealed_pipes(lp_ctx);
int global_kerberos_enctypes = lpcfg_kerberos_encryption_types(lp_ctx);
static bool warned_global_reject_md5_servers = false;
static bool warned_global_require_strong_key = false;
static bool warned_global_client_schannel = false;
static bool warned_global_seal_secure_channel = false;
static bool warned_global_kerberos_encryption_types = false;
static int warned_global_pid = 0;
int current_pid = tevent_cached_getpid();
if (warned_global_pid != current_pid) {
warned_global_reject_md5_servers = false;
warned_global_require_strong_key = false;
warned_global_client_schannel = false;
warned_global_seal_secure_channel = false;
warned_global_kerberos_encryption_types = false;
warned_global_pid = current_pid;
}
if (!global_reject_md5_servers && !warned_global_reject_md5_servers) {
/*
* We want admins to notice their misconfiguration!
*/
DBG_ERR("CVE-2022-38023 (and others): "
"Please configure 'reject md5 servers = yes' (the default), "
"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
warned_global_reject_md5_servers = true;
}
if (!global_require_strong_key && !warned_global_require_strong_key) {
/*
* We want admins to notice their misconfiguration!
*/
DBG_ERR("CVE-2022-38023 (and others): "
"Please configure 'require strong key = yes' (the default), "
"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
warned_global_require_strong_key = true;
}
if (global_client_schannel != true && !warned_global_client_schannel) {
/*
* We want admins to notice their misconfiguration!
*/
DBG_ERR("CVE-2022-38023 (and others): "
"Please configure 'client schannel = yes' (the default), "
"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
warned_global_client_schannel = true;
}
if (!global_seal_secure_channel && !warned_global_seal_secure_channel) {
/*
* We want admins to notice their misconfiguration!
*/
DBG_ERR("CVE-2022-38023 (and others): "
"Please configure 'winbind sealed pipes = yes' (the default), "
"See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
warned_global_seal_secure_channel = true;
}
if (global_kerberos_enctypes == KERBEROS_ETYPES_LEGACY &&
!warned_global_kerberos_encryption_types)
{
/*
* We want admins to notice their misconfiguration!
*/
DBG_ERR("CVE-2022-37966: "
"Please void 'kerberos encryption types = legacy', "
"See https://bugzilla.samba.org/show_bug.cgi?id=15237\n");
warned_global_kerberos_encryption_types = true;
}
}
NTSTATUS netlogon_creds_cli_context_global(struct loadparm_context *lp_ctx,
struct messaging_context *msg_ctx,
const char *client_account,
enum netr_SchannelType type,
const char *server_computer,
const char *server_netbios_domain,
const char *server_dns_domain,
TALLOC_CTX *mem_ctx,
struct netlogon_creds_cli_context **_context)
{
TALLOC_CTX *frame = talloc_stackframe();
NTSTATUS status;
struct netlogon_creds_cli_context *context = NULL;
const char *client_computer;
uint32_t proposed_flags;
uint32_t required_flags = 0;
bool reject_md5_servers = true;
bool require_strong_key = true;
int require_sign_or_seal = true;
bool seal_secure_channel = true;
enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE;
bool neutralize_nt4_emulation = false;
*_context = NULL;
if (msg_ctx == NULL) {
TALLOC_FREE(frame);
return NT_STATUS_INVALID_PARAMETER_MIX;
}
client_computer = lpcfg_netbios_name(lp_ctx);
if (strlen(client_computer) > 15) {
TALLOC_FREE(frame);
return NT_STATUS_INVALID_PARAMETER_MIX;
}
/*
* allow overwrite per domain
* reject md5 servers:
*/
reject_md5_servers = lpcfg_reject_md5_servers(lp_ctx);
reject_md5_servers = lpcfg_parm_bool(lp_ctx, NULL,
"reject md5 servers",
server_netbios_domain,
reject_md5_servers);
/*
* allow overwrite per domain
* require strong key:
*/
require_strong_key = lpcfg_require_strong_key(lp_ctx);
require_strong_key = lpcfg_parm_bool(lp_ctx, NULL,
"require strong key",
server_netbios_domain,
require_strong_key);
/*
* allow overwrite per domain
* client schannel:
*/
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:
*/
seal_secure_channel = lpcfg_winbind_sealed_pipes(lp_ctx);
seal_secure_channel = lpcfg_parm_bool(lp_ctx, NULL,
"winbind sealed pipes",
server_netbios_domain,
seal_secure_channel);
/*
* allow overwrite per domain
* neutralize nt4 emulation:
*/
neutralize_nt4_emulation = lpcfg_neutralize_nt4_emulation(lp_ctx);
neutralize_nt4_emulation = lpcfg_parm_bool(lp_ctx, NULL,
"neutralize nt4 emulation",
server_netbios_domain,
neutralize_nt4_emulation);
proposed_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS;
proposed_flags |= NETLOGON_NEG_SUPPORTS_AES;
switch (type) {
case SEC_CHAN_WKSTA:
if (lpcfg_security(lp_ctx) == SEC_ADS) {
/*
* AD domains should be secure
*/
required_flags |= NETLOGON_NEG_PASSWORD_SET2;
require_sign_or_seal = true;
require_strong_key = true;
}
break;
case SEC_CHAN_DOMAIN:
break;
case SEC_CHAN_DNS_DOMAIN:
/*
* AD domains should be secure
*/
required_flags |= NETLOGON_NEG_PASSWORD_SET2;
require_sign_or_seal = true;
require_strong_key = true;
neutralize_nt4_emulation = true;
break;
case SEC_CHAN_BDC:
required_flags |= NETLOGON_NEG_PASSWORD_SET2;
require_sign_or_seal = true;
require_strong_key = true;
break;
case SEC_CHAN_RODC:
required_flags |= NETLOGON_NEG_RODC_PASSTHROUGH;
required_flags |= NETLOGON_NEG_PASSWORD_SET2;
require_sign_or_seal = true;
require_strong_key = true;
neutralize_nt4_emulation = true;
break;
default:
TALLOC_FREE(frame);
return NT_STATUS_INVALID_PARAMETER;
}
if (neutralize_nt4_emulation) {
proposed_flags |= NETLOGON_NEG_NEUTRALIZE_NT4_EMULATION;
}
if (require_sign_or_seal) {
required_flags |= NETLOGON_NEG_ARCFOUR;
required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC;
} else {
proposed_flags &= ~NETLOGON_NEG_AUTHENTICATED_RPC;
}
if (reject_md5_servers) {
required_flags |= NETLOGON_NEG_ARCFOUR;
required_flags |= NETLOGON_NEG_PASSWORD_SET2;
required_flags |= NETLOGON_NEG_SUPPORTS_AES;
required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC;
}
if (require_strong_key) {
required_flags |= NETLOGON_NEG_ARCFOUR;
required_flags |= NETLOGON_NEG_STRONG_KEYS;
required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC;
}
/*
* If weak crypto is disabled, do not announce that we support RC4 and
* require AES.
*/
if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED) {
required_flags &= ~NETLOGON_NEG_ARCFOUR;
required_flags |= NETLOGON_NEG_SUPPORTS_AES;
proposed_flags &= ~NETLOGON_NEG_ARCFOUR;
proposed_flags |= NETLOGON_NEG_SUPPORTS_AES;
}
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)) {
TALLOC_FREE(frame);
return status;
}
context->db.g_ctx = g_lock_ctx_init(context, msg_ctx);
if (context->db.g_ctx == NULL) {
TALLOC_FREE(context);
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
status = netlogon_creds_cli_open_global_db(lp_ctx);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(context);
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
context->db.ctx = netlogon_creds_cli_global_db;
*_context = context;
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_bind_cli_credentials(
struct netlogon_creds_cli_context *context, TALLOC_CTX *mem_ctx,
struct cli_credentials **pcli_creds)
{
struct cli_credentials *cli_creds;
struct netlogon_creds_CredentialState *ncreds;
NTSTATUS status;
cli_creds = cli_credentials_init(mem_ctx);
if (cli_creds == NULL) {
return NT_STATUS_NO_MEMORY;
}
cli_credentials_set_secure_channel_type(cli_creds,
context->client.type);
cli_credentials_set_username(cli_creds, context->client.account,
CRED_SPECIFIED);
cli_credentials_set_domain(cli_creds, context->server.netbios_domain,
CRED_SPECIFIED);
cli_credentials_set_realm(cli_creds, context->server.dns_domain,
CRED_SPECIFIED);
status = netlogon_creds_cli_get(context, cli_creds, &ncreds);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(cli_creds);
return status;
}
cli_credentials_set_netlogon_creds(cli_creds, ncreds);
*pcli_creds = cli_creds;
return NT_STATUS_OK;
}
char *netlogon_creds_cli_debug_string(
const struct netlogon_creds_cli_context *context,
TALLOC_CTX *mem_ctx)
{
return talloc_asprintf(mem_ctx, "netlogon_creds_cli:%s",
context->db.key_name);
}
enum dcerpc_AuthLevel netlogon_creds_cli_auth_level(
struct netlogon_creds_cli_context *context)
{
return context->client.auth_level;
}
static bool netlogon_creds_cli_downgraded(uint32_t negotiated_flags,
uint32_t proposed_flags,
uint32_t required_flags)
{
uint32_t req_flags = required_flags;
uint32_t tmp_flags;
req_flags = required_flags;
if ((negotiated_flags & NETLOGON_NEG_SUPPORTS_AES) &&
(proposed_flags & NETLOGON_NEG_SUPPORTS_AES))
{
req_flags &= ~NETLOGON_NEG_ARCFOUR|NETLOGON_NEG_STRONG_KEYS;
}
tmp_flags = negotiated_flags;
tmp_flags &= req_flags;
if (tmp_flags != req_flags) {
return true;
}
return false;
}
struct netlogon_creds_cli_fetch_state {
TALLOC_CTX *mem_ctx;
struct netlogon_creds_CredentialState *creds;
uint32_t proposed_flags;
uint32_t required_flags;
NTSTATUS status;
};
static void netlogon_creds_cli_fetch_parser(TDB_DATA key, TDB_DATA data,
void *private_data)
{
struct netlogon_creds_cli_fetch_state *state =
(struct netlogon_creds_cli_fetch_state *)private_data;
enum ndr_err_code ndr_err;
DATA_BLOB blob;
bool downgraded;
state->creds = talloc_zero(state->mem_ctx,
struct netlogon_creds_CredentialState);
if (state->creds == NULL) {
state->status = NT_STATUS_NO_MEMORY;
return;
}
blob.data = data.dptr;
blob.length = data.dsize;
ndr_err = ndr_pull_struct_blob(&blob, state->creds, state->creds,
(ndr_pull_flags_fn_t)ndr_pull_netlogon_creds_CredentialState);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
TALLOC_FREE(state->creds);
state->status = ndr_map_error2ntstatus(ndr_err);
return;
}
if (DEBUGLEVEL >= 10) {
NDR_PRINT_DEBUG(netlogon_creds_CredentialState, state->creds);
}
downgraded = netlogon_creds_cli_downgraded(
state->creds->negotiate_flags,
state->proposed_flags,
state->required_flags);
if (downgraded) {
TALLOC_FREE(state->creds);
state->status = NT_STATUS_DOWNGRADE_DETECTED;
return;
}
state->status = NT_STATUS_OK;
}
static NTSTATUS netlogon_creds_cli_get_internal(
struct netlogon_creds_cli_context *context,
TALLOC_CTX *mem_ctx, struct netlogon_creds_CredentialState **pcreds);
NTSTATUS netlogon_creds_cli_get(struct netlogon_creds_cli_context *context,
TALLOC_CTX *mem_ctx,
struct netlogon_creds_CredentialState **_creds)
{
NTSTATUS status;
struct netlogon_creds_CredentialState *creds;
*_creds = NULL;
status = netlogon_creds_cli_get_internal(context, mem_ctx, &creds);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
/*
* mark it as invalid for step operations.
*/
creds->sequence = 0;
creds->seed = (struct netr_Credential) {{0}};
creds->client = (struct netr_Credential) {{0}};
creds->server = (struct netr_Credential) {{0}};
*_creds = creds;
return NT_STATUS_OK;
}
bool netlogon_creds_cli_validate(struct netlogon_creds_cli_context *context,
const struct netlogon_creds_CredentialState *creds1)
{
TALLOC_CTX *frame = talloc_stackframe();
struct netlogon_creds_CredentialState *creds2;
DATA_BLOB blob1;
DATA_BLOB blob2;
NTSTATUS status;
enum ndr_err_code ndr_err;
bool equal;
status = netlogon_creds_cli_get(context, frame, &creds2);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(frame);
return false;
}
ndr_err = ndr_push_struct_blob(&blob1, frame, creds1,
(ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
TALLOC_FREE(frame);
return false;
}
ndr_err = ndr_push_struct_blob(&blob2, frame, creds2,
(ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
TALLOC_FREE(frame);
return false;
}
equal = data_blob_equal_const_time(&blob1, &blob2);
TALLOC_FREE(frame);
return equal;
}
static NTSTATUS netlogon_creds_cli_store_internal(
struct netlogon_creds_cli_context *context,
struct netlogon_creds_CredentialState *creds)
{
NTSTATUS status;
enum ndr_err_code ndr_err;
DATA_BLOB blob;
TDB_DATA data;
if (DEBUGLEVEL >= 10) {
NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds);
}
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)) {
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(data.dptr);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_store(struct netlogon_creds_cli_context *context,
struct netlogon_creds_CredentialState *creds)
{
NTSTATUS status;
if (context->db.locked_state == NULL) {
/*
* this was not the result of netlogon_creds_cli_lock*()
*/
return NT_STATUS_INVALID_PAGE_PROTECTION;
}
if (context->db.locked_state->creds != creds) {
/*
* this was not the result of netlogon_creds_cli_lock*()
*/
return NT_STATUS_INVALID_PAGE_PROTECTION;
}
status = netlogon_creds_cli_store_internal(context, creds);
return status;
}
static NTSTATUS netlogon_creds_cli_delete_internal(
struct netlogon_creds_cli_context *context)
{
NTSTATUS status;
status = dbwrap_delete(context->db.ctx, context->db.key_data);
return status;
}
NTSTATUS netlogon_creds_cli_delete_lck(
struct netlogon_creds_cli_context *context)
{
NTSTATUS status;
if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) {
return NT_STATUS_NOT_LOCKED;
}
status = netlogon_creds_cli_delete_internal(context);
return status;
}
NTSTATUS netlogon_creds_cli_delete(struct netlogon_creds_cli_context *context,
struct netlogon_creds_CredentialState *creds)
{
NTSTATUS status;
if (context->db.locked_state == NULL) {
/*
* this was not the result of netlogon_creds_cli_lock*()
*/
return NT_STATUS_INVALID_PAGE_PROTECTION;
}
if (context->db.locked_state->creds != creds) {
/*
* this was not the result of netlogon_creds_cli_lock*()
*/
return NT_STATUS_INVALID_PAGE_PROTECTION;
}
status = netlogon_creds_cli_delete_internal(context);
return status;
}
struct netlogon_creds_cli_lock_state {
struct netlogon_creds_cli_locked_state *locked_state;
struct netlogon_creds_CredentialState *creds;
};
static void netlogon_creds_cli_lock_done(struct tevent_req *subreq);
struct tevent_req *netlogon_creds_cli_lock_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context)
{
struct tevent_req *req;
struct netlogon_creds_cli_lock_state *state;
struct netlogon_creds_cli_locked_state *locked_state;
struct tevent_req *subreq;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_lock_state);
if (req == NULL) {
return NULL;
}
if (context->db.locked_state != NULL) {
tevent_req_nterror(req, NT_STATUS_LOCK_NOT_GRANTED);
return tevent_req_post(req, ev);
}
locked_state = talloc_zero(state, struct netlogon_creds_cli_locked_state);
if (tevent_req_nomem(locked_state, req)) {
return tevent_req_post(req, ev);
}
talloc_set_destructor(locked_state,
netlogon_creds_cli_locked_state_destructor);
locked_state->context = context;
context->db.locked_state = locked_state;
state->locked_state = locked_state;
if (context->db.g_ctx == NULL) {
NTSTATUS status;
status = netlogon_creds_cli_get_internal(
context, state, &state->creds);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
return req;
}
subreq = g_lock_lock_send(state, ev,
context->db.g_ctx,
string_term_tdb_data(context->db.key_name),
G_LOCK_WRITE,
NULL, NULL);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, netlogon_creds_cli_lock_done, req);
return req;
}
static void netlogon_creds_cli_lock_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_lock_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_lock_state);
NTSTATUS status;
status = g_lock_lock_recv(subreq);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
state->locked_state->is_glocked = true;
status = netlogon_creds_cli_get_internal(state->locked_state->context,
state, &state->creds);
if (tevent_req_nterror(req, status)) {
return;
}
tevent_req_done(req);
}
static NTSTATUS netlogon_creds_cli_get_internal(
struct netlogon_creds_cli_context *context,
TALLOC_CTX *mem_ctx, struct netlogon_creds_CredentialState **pcreds)
{
struct netlogon_creds_cli_fetch_state fstate = {
.status = NT_STATUS_INTERNAL_ERROR,
.proposed_flags = context->client.proposed_flags,
.required_flags = context->client.required_flags,
};
NTSTATUS status;
fstate.mem_ctx = mem_ctx;
status = dbwrap_parse_record(context->db.ctx,
context->db.key_data,
netlogon_creds_cli_fetch_parser,
&fstate);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
if (!NT_STATUS_IS_OK(fstate.status)) {
return fstate.status;
}
if (context->server.cached_flags == fstate.creds->negotiate_flags) {
*pcreds = fstate.creds;
return NT_STATUS_OK;
}
/*
* It is really important to try SamLogonEx here,
* because multiple processes can talk to the same
* domain controller, without using the credential
* chain.
*
* With a normal SamLogon call, we must keep the
* credentials chain updated and intact between all
* users of the machine account (which would imply
* cross-node communication for every NTLM logon).
*
* The credentials chain is not per NETLOGON pipe
* connection, but globally on the server/client pair
* by computer name.
*
* It's also important to use NetlogonValidationSamInfo4 (6),
* because it relies on the rpc transport encryption
* and avoids using the global netlogon schannel
* session key to en/decrypt secret information
* like the user_session_key for network logons.
*
* [MS-APDS] 3.1.5.2 NTLM Network Logon
* says NETLOGON_NEG_CROSS_FOREST_TRUSTS and
* NETLOGON_NEG_AUTHENTICATED_RPC set together
* are the indication that the server supports
* NetlogonValidationSamInfo4 (6). And it must only
* be used if "SealSecureChannel" is used.
*
* The "SealSecureChannel" AUTH_TYPE_SCHANNEL/AUTH_LEVEL_PRIVACY
* check is done in netlogon_creds_cli_LogonSamLogon*().
*/
context->server.cached_flags = fstate.creds->negotiate_flags;
context->server.try_validation6 = true;
context->server.try_logon_ex = true;
context->server.try_logon_with = true;
if (!(context->server.cached_flags & NETLOGON_NEG_AUTHENTICATED_RPC)) {
context->server.try_validation6 = false;
context->server.try_logon_ex = false;
}
if (!(context->server.cached_flags & NETLOGON_NEG_CROSS_FOREST_TRUSTS)) {
context->server.try_validation6 = false;
}
*pcreds = fstate.creds;
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_lock_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
struct netlogon_creds_CredentialState **creds)
{
struct netlogon_creds_cli_lock_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_lock_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
tevent_req_received(req);
return status;
}
talloc_steal(state->creds, state->locked_state);
state->locked_state->creds = state->creds;
*creds = talloc_move(mem_ctx, &state->creds);
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_lock(struct netlogon_creds_cli_context *context,
TALLOC_CTX *mem_ctx,
struct netlogon_creds_CredentialState **creds)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_lock_send(frame, ev, context);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_lock_recv(req, mem_ctx, creds);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_lck {
struct netlogon_creds_cli_context *context;
};
struct netlogon_creds_cli_lck_state {
struct netlogon_creds_cli_lck *lck;
enum netlogon_creds_cli_lck_type type;
};
static void netlogon_creds_cli_lck_locked(struct tevent_req *subreq);
static int netlogon_creds_cli_lck_destructor(
struct netlogon_creds_cli_lck *lck);
struct tevent_req *netlogon_creds_cli_lck_send(
TALLOC_CTX *mem_ctx, struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
enum netlogon_creds_cli_lck_type type)
{
struct tevent_req *req, *subreq;
struct netlogon_creds_cli_lck_state *state;
enum g_lock_type gtype;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_lck_state);
if (req == NULL) {
return NULL;
}
if (context->db.lock != NETLOGON_CREDS_CLI_LCK_NONE) {
DBG_DEBUG("context already locked\n");
tevent_req_nterror(req, NT_STATUS_INVALID_LOCK_SEQUENCE);
return tevent_req_post(req, ev);
}
switch (type) {
case NETLOGON_CREDS_CLI_LCK_SHARED:
gtype = G_LOCK_READ;
break;
case NETLOGON_CREDS_CLI_LCK_EXCLUSIVE:
gtype = G_LOCK_WRITE;
break;
default:
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
return tevent_req_post(req, ev);
}
state->lck = talloc(state, struct netlogon_creds_cli_lck);
if (tevent_req_nomem(state->lck, req)) {
return tevent_req_post(req, ev);
}
state->lck->context = context;
state->type = type;
subreq = g_lock_lock_send(state, ev,
context->db.g_ctx,
string_term_tdb_data(context->db.key_name),
gtype,
NULL, NULL);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, netlogon_creds_cli_lck_locked, req);
return req;
}
static void netlogon_creds_cli_lck_locked(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct netlogon_creds_cli_lck_state *state = tevent_req_data(
req, struct netlogon_creds_cli_lck_state);
NTSTATUS status;
status = g_lock_lock_recv(subreq);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
state->lck->context->db.lock = state->type;
talloc_set_destructor(state->lck, netlogon_creds_cli_lck_destructor);
tevent_req_done(req);
}
static int netlogon_creds_cli_lck_destructor(
struct netlogon_creds_cli_lck *lck)
{
struct netlogon_creds_cli_context *ctx = lck->context;
NTSTATUS status;
status = g_lock_unlock(ctx->db.g_ctx,
string_term_tdb_data(ctx->db.key_name));
if (!NT_STATUS_IS_OK(status)) {
DBG_WARNING("g_lock_unlock failed: %s\n", nt_errstr(status));
smb_panic("g_lock_unlock failed");
}
ctx->db.lock = NETLOGON_CREDS_CLI_LCK_NONE;
return 0;
}
NTSTATUS netlogon_creds_cli_lck_recv(
struct tevent_req *req, TALLOC_CTX *mem_ctx,
struct netlogon_creds_cli_lck **lck)
{
struct netlogon_creds_cli_lck_state *state = tevent_req_data(
req, struct netlogon_creds_cli_lck_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
return status;
}
*lck = talloc_move(mem_ctx, &state->lck);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_lck(
struct netlogon_creds_cli_context *context,
enum netlogon_creds_cli_lck_type type,
TALLOC_CTX *mem_ctx, struct netlogon_creds_cli_lck **lck)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_lck_send(frame, ev, context, type);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_lck_recv(req, mem_ctx, lck);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_auth_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
uint8_t num_nt_hashes;
uint8_t idx_nt_hashes;
const struct samr_Password * const *nt_hashes;
const struct samr_Password *used_nt_hash;
char *srv_name_slash;
uint32_t current_flags;
struct netr_Credential client_challenge;
struct netr_Credential server_challenge;
struct netlogon_creds_CredentialState *creds;
struct netr_Credential client_credential;
struct netr_Credential server_credential;
uint32_t rid;
bool try_auth3;
bool try_auth2;
bool require_auth2;
};
static void netlogon_creds_cli_auth_challenge_start(struct tevent_req *req);
struct tevent_req *netlogon_creds_cli_auth_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
uint8_t num_nt_hashes,
const struct samr_Password * const *nt_hashes)
{
struct tevent_req *req;
struct netlogon_creds_cli_auth_state *state;
NTSTATUS status;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_auth_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
if (num_nt_hashes < 1) {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
return tevent_req_post(req, ev);
}
if (num_nt_hashes > 4) {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
return tevent_req_post(req, ev);
}
state->num_nt_hashes = num_nt_hashes;
state->idx_nt_hashes = 0;
state->nt_hashes = nt_hashes;
if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) {
tevent_req_nterror(req, NT_STATUS_NOT_LOCKED);
return tevent_req_post(req, ev);
}
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
state->try_auth3 = true;
state->try_auth2 = true;
if (context->client.required_flags != 0) {
state->require_auth2 = true;
}
state->used_nt_hash = state->nt_hashes[state->idx_nt_hashes];
state->current_flags = context->client.proposed_flags;
status = dbwrap_purge(state->context->db.ctx,
state->context->db.key_data);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
netlogon_creds_cli_auth_challenge_start(req);
if (!tevent_req_is_in_progress(req)) {
return tevent_req_post(req, ev);
}
return req;
}
static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq);
static void netlogon_creds_cli_auth_challenge_start(struct tevent_req *req)
{
struct netlogon_creds_cli_auth_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_auth_state);
struct tevent_req *subreq;
TALLOC_FREE(state->creds);
netlogon_creds_random_challenge(&state->client_challenge);
subreq = dcerpc_netr_ServerReqChallenge_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->context->client.computer,
&state->client_challenge,
&state->server_challenge);
if (tevent_req_nomem(subreq, req)) {
return;
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_auth_challenge_done,
req);
}
static void netlogon_creds_cli_auth_srvauth_done(struct tevent_req *subreq);
static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_auth_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_auth_state);
NTSTATUS status;
NTSTATUS result;
status = dcerpc_netr_ServerReqChallenge_recv(subreq, state, &result);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
if (tevent_req_nterror(req, result)) {
return;
}
if (!state->try_auth3 && !state->try_auth2) {
state->current_flags = 0;
}
/* Calculate the session key and client credentials */
state->creds = netlogon_creds_client_init(state,
state->context->client.account,
state->context->client.computer,
state->context->client.type,
&state->client_challenge,
&state->server_challenge,
state->used_nt_hash,
&state->client_credential,
state->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;
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;
bool downgraded;
if (state->try_auth3) {
status = dcerpc_netr_ServerAuthenticate3_recv(subreq, state,
&result);
TALLOC_FREE(subreq);
if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
state->try_auth3 = false;
netlogon_creds_cli_auth_challenge_start(req);
return;
}
if (tevent_req_nterror(req, status)) {
return;
}
} else if (state->try_auth2) {
status = dcerpc_netr_ServerAuthenticate2_recv(subreq, state,
&result);
TALLOC_FREE(subreq);
if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
state->try_auth2 = false;
if (state->require_auth2) {
status = NT_STATUS_DOWNGRADE_DETECTED;
tevent_req_nterror(req, status);
return;
}
netlogon_creds_cli_auth_challenge_start(req);
return;
}
if (tevent_req_nterror(req, status)) {
return;
}
} else {
status = dcerpc_netr_ServerAuthenticate_recv(subreq, state,
&result);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
}
if (!NT_STATUS_IS_OK(result) &&
!NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED))
{
tevent_req_nterror(req, result);
return;
}
downgraded = netlogon_creds_cli_downgraded(
state->creds->negotiate_flags,
state->context->client.proposed_flags,
state->context->client.required_flags);
if (downgraded) {
if (NT_STATUS_IS_OK(result)) {
tevent_req_nterror(req, NT_STATUS_DOWNGRADE_DETECTED);
return;
}
tevent_req_nterror(req, result);
return;
}
if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) {
uint32_t 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;
}
state->idx_nt_hashes += 1;
if (state->idx_nt_hashes >= state->num_nt_hashes) {
/*
* we already retried, giving up...
*/
tevent_req_nterror(req, result);
return;
}
/*
* lets retry with the old nt hash.
*/
state->used_nt_hash = state->nt_hashes[state->idx_nt_hashes];
state->current_flags = state->context->client.proposed_flags;
netlogon_creds_cli_auth_challenge_start(req);
return;
}
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);
if (tevent_req_nterror(req, status)) {
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_auth_recv(struct tevent_req *req,
uint8_t *idx_nt_hashes)
{
struct netlogon_creds_cli_auth_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_auth_state);
NTSTATUS status;
*idx_nt_hashes = 0;
if (tevent_req_is_nterror(req, &status)) {
tevent_req_received(req);
return status;
}
*idx_nt_hashes = state->idx_nt_hashes;
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_auth(struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
uint8_t num_nt_hashes,
const struct samr_Password * const *nt_hashes,
uint8_t *idx_nt_hashes)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
*idx_nt_hashes = 0;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_auth_send(frame, ev, context, b,
num_nt_hashes, nt_hashes);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_auth_recv(req, idx_nt_hashes);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_check_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
char *srv_name_slash;
union netr_Capabilities caps;
struct netlogon_creds_CredentialState *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_caps(struct tevent_req *subreq);
struct tevent_req *netlogon_creds_cli_check_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b)
{
struct tevent_req *req;
struct netlogon_creds_cli_check_state *state;
struct tevent_req *subreq;
enum dcerpc_AuthType auth_type;
enum dcerpc_AuthLevel auth_level;
NTSTATUS status;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_check_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) {
tevent_req_nterror(req, NT_STATUS_NOT_LOCKED);
return tevent_req_post(req, ev);
}
status = netlogon_creds_cli_get_internal(context, state,
&state->creds);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
dcerpc_binding_handle_auth_info(state->binding_handle,
&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);
}
/*
* we defer all callbacks in order to cleanup
* the database record.
*/
tevent_req_defer_callback(req, state->ev);
status = netlogon_creds_client_authenticator(state->creds,
&state->req_auth);
if (tevent_req_nterror(req, status)) {
return tevent_req_post(req, ev);
}
ZERO_STRUCT(state->rep_auth);
subreq = dcerpc_netr_LogonGetCapabilities_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->context->client.computer,
&state->req_auth,
&state->rep_auth,
1,
&state->caps);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_check_caps,
req);
return req;
}
static void netlogon_creds_cli_check_cleanup(struct tevent_req *req,
NTSTATUS status)
{
struct netlogon_creds_cli_check_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_check_state);
if (state->creds == NULL) {
return;
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
!NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
TALLOC_FREE(state->creds);
return;
}
netlogon_creds_cli_delete_lck(state->context);
TALLOC_FREE(state->creds);
}
static void netlogon_creds_cli_check_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)) {
/*
* Note that the negotiated flags are already checked
* for our required flags after the ServerAuthenticate3/2 call.
*/
uint32_t negotiated = state->creds->negotiate_flags;
if (negotiated & NETLOGON_NEG_SUPPORTS_AES) {
/*
* If we have negotiated NETLOGON_NEG_SUPPORTS_AES
* already, we expect this to work!
*/
status = NT_STATUS_DOWNGRADE_DETECTED;
tevent_req_nterror(req, status);
netlogon_creds_cli_check_cleanup(req, status);
return;
}
if (negotiated & NETLOGON_NEG_STRONG_KEYS) {
/*
* If we have negotiated NETLOGON_NEG_STRONG_KEYS
* we expect this to work at least as far as the
* NOT_SUPPORTED error handled below!
*
* NT 4.0 and Old Samba servers are not
* allowed without "require strong key = no"
*/
status = NT_STATUS_DOWNGRADE_DETECTED;
tevent_req_nterror(req, status);
netlogon_creds_cli_check_cleanup(req, status);
return;
}
/*
* If we not require NETLOGON_NEG_SUPPORTS_AES or
* NETLOGON_NEG_STRONG_KEYS, it's ok to ignore
* NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE.
*
* This is needed against NT 4.0 and old Samba servers.
*
* As we're using DCERPC_AUTH_TYPE_SCHANNEL with
* DCERPC_AUTH_LEVEL_INTEGRITY or DCERPC_AUTH_LEVEL_PRIVACY
* we should detect a faked NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE
* with the next request as the sequence number processing
* gets out of sync.
*/
netlogon_creds_cli_check_cleanup(req, status);
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)) {
/*
* Note that the negotiated flags are already checked
* for our required flags after the ServerAuthenticate3/2 call.
*/
uint32_t negotiated = state->creds->negotiate_flags;
if (negotiated & NETLOGON_NEG_SUPPORTS_AES) {
/*
* If we have negotiated NETLOGON_NEG_SUPPORTS_AES
* already, we expect this to work!
*/
status = NT_STATUS_DOWNGRADE_DETECTED;
tevent_req_nterror(req, status);
netlogon_creds_cli_check_cleanup(req, status);
return;
}
/*
* This is ok, the server does not support
* NETLOGON_NEG_SUPPORTS_AES.
*
* netr_LogonGetCapabilities() was
* netr_LogonDummyRoutine1() before
* NETLOGON_NEG_SUPPORTS_AES was invented.
*/
netlogon_creds_cli_check_cleanup(req, result);
tevent_req_done(req);
return;
}
ok = netlogon_creds_client_check(state->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->creds->negotiate_flags) {
status = NT_STATUS_DOWNGRADE_DETECTED;
tevent_req_nterror(req, status);
netlogon_creds_cli_check_cleanup(req, status);
return;
}
/*
* This is the key check that makes this check secure. If we
* get OK here (rather than NOT_SUPPORTED), then the server
* did support AES. If the server only proposed STRONG_KEYS
* and not AES, then it should have failed with
* NOT_IMPLEMENTED. We always send AES as a client, so the
* server should always have returned it.
*/
if (!(state->caps.server_capabilities & NETLOGON_NEG_SUPPORTS_AES)) {
status = NT_STATUS_DOWNGRADE_DETECTED;
tevent_req_nterror(req, status);
netlogon_creds_cli_check_cleanup(req, status);
return;
}
status = netlogon_creds_cli_store_internal(state->context,
state->creds);
if (tevent_req_nterror(req, status)) {
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_check_recv(struct tevent_req *req,
union netr_Capabilities *capabilities)
{
struct netlogon_creds_cli_check_state *state = tevent_req_data(
req, struct netlogon_creds_cli_check_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
netlogon_creds_cli_check_cleanup(req, status);
tevent_req_received(req);
return status;
}
if (capabilities != NULL) {
*capabilities = state->caps;
}
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_check(struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
union netr_Capabilities *capabilities)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_check_send(frame, ev, context, b);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_check_recv(req, capabilities);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_ServerPasswordSet_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
uint32_t old_timeout;
char *srv_name_slash;
enum dcerpc_AuthType auth_type;
enum dcerpc_AuthLevel auth_level;
struct samr_CryptPassword samr_crypt_password;
struct netr_CryptPassword netr_crypt_password;
struct samr_Password samr_password;
struct netlogon_creds_CredentialState *creds;
struct netlogon_creds_CredentialState tmp_creds;
struct netr_Authenticator req_auth;
struct netr_Authenticator rep_auth;
};
static void netlogon_creds_cli_ServerPasswordSet_cleanup(struct tevent_req *req,
NTSTATUS status);
static void netlogon_creds_cli_ServerPasswordSet_locked(struct tevent_req *subreq);
struct tevent_req *netlogon_creds_cli_ServerPasswordSet_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
const DATA_BLOB *new_password,
const uint32_t *new_version)
{
struct tevent_req *req;
struct netlogon_creds_cli_ServerPasswordSet_state *state;
struct tevent_req *subreq;
bool ok;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_ServerPasswordSet_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
if (new_password->length < 14) {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
return tevent_req_post(req, ev);
}
/*
* netr_ServerPasswordSet
*/
mdfour(state->samr_password.hash, new_password->data, new_password->length);
/*
* netr_ServerPasswordSet2
*/
ok = set_pw_in_buffer(state->samr_crypt_password.data,
new_password);
if (!ok) {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
return tevent_req_post(req, ev);
}
if (new_version != NULL) {
struct NL_PASSWORD_VERSION version;
uint32_t len = IVAL(state->samr_crypt_password.data, 512);
uint32_t ofs = 512 - len;
uint8_t *p;
if (len > 500) {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
return tevent_req_post(req, ev);
}
ofs -= 12;
version.ReservedField = 0;
version.PasswordVersionNumber = *new_version;
version.PasswordVersionPresent =
NETLOGON_PASSWORD_VERSION_NUMBER_PRESENT;
p = state->samr_crypt_password.data + ofs;
SIVAL(p, 0, version.ReservedField);
SIVAL(p, 4, version.PasswordVersionNumber);
SIVAL(p, 8, version.PasswordVersionPresent);
}
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
dcerpc_binding_handle_auth_info(state->binding_handle,
&state->auth_type,
&state->auth_level);
subreq = netlogon_creds_cli_lock_send(state, state->ev,
state->context);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_ServerPasswordSet_locked,
req);
return req;
}
static void netlogon_creds_cli_ServerPasswordSet_cleanup(struct tevent_req *req,
NTSTATUS status)
{
struct netlogon_creds_cli_ServerPasswordSet_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_ServerPasswordSet_state);
if (state->creds == NULL) {
return;
}
dcerpc_binding_handle_set_timeout(state->binding_handle,
state->old_timeout);
if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
!NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
TALLOC_FREE(state->creds);
return;
}
netlogon_creds_cli_delete(state->context, state->creds);
TALLOC_FREE(state->creds);
}
static void netlogon_creds_cli_ServerPasswordSet_done(struct tevent_req *subreq);
static void netlogon_creds_cli_ServerPasswordSet_locked(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_ServerPasswordSet_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_ServerPasswordSet_state);
NTSTATUS status;
status = netlogon_creds_cli_lock_recv(subreq, state,
&state->creds);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
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;
status = netlogon_creds_client_authenticator(&state->tmp_creds,
&state->req_auth);
if (tevent_req_nterror(req, status)) {
return;
}
ZERO_STRUCT(state->rep_auth);
if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_PASSWORD_SET2) {
if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
status = netlogon_creds_aes_encrypt(&state->tmp_creds,
state->samr_crypt_password.data,
516);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
return;
}
} else {
status = netlogon_creds_arcfour_crypt(&state->tmp_creds,
state->samr_crypt_password.data,
516);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
return;
}
}
memcpy(state->netr_crypt_password.data,
state->samr_crypt_password.data, 512);
state->netr_crypt_password.length =
IVAL(state->samr_crypt_password.data, 512);
subreq = dcerpc_netr_ServerPasswordSet2_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->tmp_creds.account_name,
state->tmp_creds.secure_channel_type,
state->tmp_creds.computer_name,
&state->req_auth,
&state->rep_auth,
&state->netr_crypt_password);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
return;
}
} else {
status = netlogon_creds_des_encrypt(&state->tmp_creds,
&state->samr_password);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
return;
}
subreq = dcerpc_netr_ServerPasswordSet_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->tmp_creds.account_name,
state->tmp_creds.secure_channel_type,
state->tmp_creds.computer_name,
&state->req_auth,
&state->rep_auth,
&state->samr_password);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
return;
}
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_ServerPasswordSet_done,
req);
}
static void netlogon_creds_cli_ServerPasswordSet_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_ServerPasswordSet_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_ServerPasswordSet_state);
NTSTATUS status;
NTSTATUS result;
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);
TALLOC_FREE(state->creds);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_ServerPasswordSet_recv(struct tevent_req *req)
{
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
netlogon_creds_cli_ServerPasswordSet_cleanup(req, status);
tevent_req_received(req);
return status;
}
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_ServerPasswordSet(
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
const DATA_BLOB *new_password,
const uint32_t *new_version)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_ServerPasswordSet_send(frame, ev, context, b,
new_password,
new_version);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_ServerPasswordSet_recv(req);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_LogonSamLogon_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
char *srv_name_slash;
enum netr_LogonInfoClass logon_level;
const union netr_LogonLevel *const_logon;
union netr_LogonLevel *logon;
uint32_t flags;
uint16_t validation_level;
union netr_Validation *validation;
uint8_t authoritative;
/*
* do we need encryption at the application layer?
*/
bool user_encrypt;
bool try_logon_ex;
bool try_validation6;
/*
* the read only credentials before we started the operation
* used for netr_LogonSamLogonEx() if required (validation_level = 3).
*/
struct netlogon_creds_CredentialState *ro_creds;
/*
* The (locked) credentials used for the credential chain
* used for netr_LogonSamLogonWithFlags() or
* netr_LogonSamLogonWith().
*/
struct netlogon_creds_CredentialState *lk_creds;
/*
* While we have locked the global credentials (lk_creds above)
* we operate an a temporary copy, because a server
* may not support netr_LogonSamLogonWithFlags() and
* didn't process our netr_Authenticator, so we need to
* restart from lk_creds.
*/
struct netlogon_creds_CredentialState tmp_creds;
struct netr_Authenticator req_auth;
struct netr_Authenticator rep_auth;
};
static void netlogon_creds_cli_LogonSamLogon_start(struct tevent_req *req);
static void netlogon_creds_cli_LogonSamLogon_cleanup(struct tevent_req *req,
NTSTATUS status);
struct tevent_req *netlogon_creds_cli_LogonSamLogon_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
enum netr_LogonInfoClass logon_level,
const union netr_LogonLevel *logon,
uint32_t flags)
{
struct tevent_req *req;
struct netlogon_creds_cli_LogonSamLogon_state *state;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_LogonSamLogon_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
state->logon_level = logon_level;
state->const_logon = logon;
state->flags = flags;
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
switch (logon_level) {
case NetlogonInteractiveInformation:
case NetlogonInteractiveTransitiveInformation:
case NetlogonServiceInformation:
case NetlogonServiceTransitiveInformation:
case NetlogonGenericInformation:
state->user_encrypt = true;
break;
case NetlogonNetworkInformation:
case NetlogonNetworkTransitiveInformation:
break;
}
state->validation = talloc_zero(state, union netr_Validation);
if (tevent_req_nomem(state->validation, req)) {
return tevent_req_post(req, ev);
}
netlogon_creds_cli_LogonSamLogon_start(req);
if (!tevent_req_is_in_progress(req)) {
return tevent_req_post(req, ev);
}
/*
* we defer all callbacks in order to cleanup
* the database record.
*/
tevent_req_defer_callback(req, state->ev);
return req;
}
static void netlogon_creds_cli_LogonSamLogon_cleanup(struct tevent_req *req,
NTSTATUS status)
{
struct netlogon_creds_cli_LogonSamLogon_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_LogonSamLogon_state);
if (state->lk_creds == NULL) {
return;
}
if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
/*
* This is a hack to recover from a bug in old
* Samba servers, when LogonSamLogonEx() fails:
*
* api_net_sam_logon_ex: Failed to marshall NET_R_SAM_LOGON_EX.
*
* All following request will get NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE.
*
* A second bug generates NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE,
* instead of NT_STATUS_ACCESS_DENIED or NT_STATUS_RPC_SEC_PKG_ERROR
* If the sign/seal check fails.
*
* In that case we need to cleanup the netlogon session.
*
* It's the job of the caller to disconnect the current
* connection, if netlogon_creds_cli_LogonSamLogon()
* returns NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE.
*/
if (!state->context->server.try_logon_with) {
status = NT_STATUS_NETWORK_ACCESS_DENIED;
}
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
!NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
TALLOC_FREE(state->lk_creds);
return;
}
netlogon_creds_cli_delete(state->context, state->lk_creds);
TALLOC_FREE(state->lk_creds);
}
static void netlogon_creds_cli_LogonSamLogon_done(struct tevent_req *subreq);
static void netlogon_creds_cli_LogonSamLogon_start(struct tevent_req *req)
{
struct netlogon_creds_cli_LogonSamLogon_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_LogonSamLogon_state);
struct tevent_req *subreq;
NTSTATUS status;
enum dcerpc_AuthType auth_type;
enum dcerpc_AuthLevel auth_level;
TALLOC_FREE(state->ro_creds);
TALLOC_FREE(state->logon);
ZERO_STRUCTP(state->validation);
dcerpc_binding_handle_auth_info(state->binding_handle,
&auth_type, &auth_level);
state->try_logon_ex = state->context->server.try_logon_ex;
state->try_validation6 = state->context->server.try_validation6;
if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) {
state->try_logon_ex = false;
}
if (auth_level != DCERPC_AUTH_LEVEL_PRIVACY) {
state->try_validation6 = false;
}
if (state->try_logon_ex) {
if (state->try_validation6) {
state->validation_level = 6;
} else {
state->validation_level = 3;
state->user_encrypt = true;
}
state->logon = netlogon_creds_shallow_copy_logon(state,
state->logon_level,
state->const_logon);
if (tevent_req_nomem(state->logon, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
if (state->user_encrypt) {
status = netlogon_creds_cli_get(state->context,
state,
&state->ro_creds);
if (!NT_STATUS_IS_OK(status)) {
status = NT_STATUS_ACCESS_DENIED;
tevent_req_nterror(req, status);
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
status = netlogon_creds_encrypt_samlogon_logon(state->ro_creds,
state->logon_level,
state->logon);
if (!NT_STATUS_IS_OK(status)) {
status = NT_STATUS_ACCESS_DENIED;
tevent_req_nterror(req, status);
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
}
subreq = dcerpc_netr_LogonSamLogonEx_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->context->client.computer,
state->logon_level,
state->logon,
state->validation_level,
state->validation,
&state->authoritative,
&state->flags);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_LogonSamLogon_done,
req);
return;
}
if (state->lk_creds == NULL) {
subreq = netlogon_creds_cli_lock_send(state, state->ev,
state->context);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_LogonSamLogon_done,
req);
return;
}
state->tmp_creds = *state->lk_creds;
status = netlogon_creds_client_authenticator(&state->tmp_creds,
&state->req_auth);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
ZERO_STRUCT(state->rep_auth);
state->logon = netlogon_creds_shallow_copy_logon(state,
state->logon_level,
state->const_logon);
if (tevent_req_nomem(state->logon, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
status = netlogon_creds_encrypt_samlogon_logon(&state->tmp_creds,
state->logon_level,
state->logon);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
state->validation_level = 3;
if (state->context->server.try_logon_with) {
subreq = dcerpc_netr_LogonSamLogonWithFlags_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->context->client.computer,
&state->req_auth,
&state->rep_auth,
state->logon_level,
state->logon,
state->validation_level,
state->validation,
&state->authoritative,
&state->flags);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
} else {
state->flags = 0;
subreq = dcerpc_netr_LogonSamLogon_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->context->client.computer,
&state->req_auth,
&state->rep_auth,
state->logon_level,
state->logon,
state->validation_level,
state->validation,
&state->authoritative);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_LogonSamLogon_done,
req);
}
static void netlogon_creds_cli_LogonSamLogon_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_LogonSamLogon_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_LogonSamLogon_state);
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.
*
* netlogon_creds_cli_LogonSamLogon_start()
* will TALLOC_FREE(state->ro_creds);
*/
state->try_logon_ex = false;
netlogon_creds_cli_LogonSamLogon_start(req);
return;
}
status = netlogon_creds_decrypt_samlogon_validation(state->ro_creds,
state->validation_level,
state->validation);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
tevent_req_done(req);
return;
}
if (state->lk_creds == NULL) {
status = netlogon_creds_cli_lock_recv(subreq, state,
&state->lk_creds);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
netlogon_creds_cli_LogonSamLogon_start(req);
return;
}
if (state->context->server.try_logon_with) {
status = dcerpc_netr_LogonSamLogonWithFlags_recv(subreq,
state->validation,
&result);
TALLOC_FREE(subreq);
if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
state->context->server.try_logon_with = false;
netlogon_creds_cli_LogonSamLogon_start(req);
return;
}
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
} else {
status = dcerpc_netr_LogonSamLogon_recv(subreq,
state->validation,
&result);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
}
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);
TALLOC_FREE(state->lk_creds);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
return;
}
if (tevent_req_nterror(req, result)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, result);
return;
}
status = netlogon_creds_decrypt_samlogon_validation(&state->tmp_creds,
state->validation_level,
state->validation);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, result);
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_LogonSamLogon_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
uint16_t *validation_level,
union netr_Validation **validation,
uint8_t *authoritative,
uint32_t *flags)
{
struct netlogon_creds_cli_LogonSamLogon_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_LogonSamLogon_state);
NTSTATUS status;
/* authoritative is also returned on error */
*authoritative = state->authoritative;
if (tevent_req_is_nterror(req, &status)) {
netlogon_creds_cli_LogonSamLogon_cleanup(req, status);
tevent_req_received(req);
return status;
}
*validation_level = state->validation_level;
*validation = talloc_move(mem_ctx, &state->validation);
*flags = state->flags;
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_LogonSamLogon(
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
enum netr_LogonInfoClass logon_level,
const union netr_LogonLevel *logon,
TALLOC_CTX *mem_ctx,
uint16_t *validation_level,
union netr_Validation **validation,
uint8_t *authoritative,
uint32_t *flags)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_LogonSamLogon_send(frame, ev, context, b,
logon_level, logon,
*flags);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_LogonSamLogon_recv(req, mem_ctx,
validation_level,
validation,
authoritative,
flags);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
char *srv_name_slash;
enum dcerpc_AuthType auth_type;
enum dcerpc_AuthLevel auth_level;
const char *site_name;
uint32_t dns_ttl;
struct NL_DNS_NAME_INFO_ARRAY *dns_names;
struct netlogon_creds_CredentialState *creds;
struct netlogon_creds_CredentialState tmp_creds;
struct netr_Authenticator req_auth;
struct netr_Authenticator rep_auth;
};
static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(struct tevent_req *req,
NTSTATUS status);
static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked(struct tevent_req *subreq);
struct tevent_req *netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
const char *site_name,
uint32_t dns_ttl,
struct NL_DNS_NAME_INFO_ARRAY *dns_names)
{
struct tevent_req *req;
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state;
struct tevent_req *subreq;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
state->site_name = site_name;
state->dns_ttl = dns_ttl;
state->dns_names = dns_names;
dcerpc_binding_handle_auth_info(state->binding_handle,
&state->auth_type,
&state->auth_level);
subreq = netlogon_creds_cli_lock_send(state, state->ev,
state->context);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked,
req);
return req;
}
static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(struct tevent_req *req,
NTSTATUS status)
{
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state);
if (state->creds == NULL) {
return;
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
!NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
TALLOC_FREE(state->creds);
return;
}
netlogon_creds_cli_delete(state->context, state->creds);
TALLOC_FREE(state->creds);
}
static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done(struct tevent_req *subreq);
static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state);
NTSTATUS status;
status = netlogon_creds_cli_lock_recv(subreq, state,
&state->creds);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
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;
}
}
/*
* we defer all callbacks in order to cleanup
* the database record.
*/
tevent_req_defer_callback(req, state->ev);
state->tmp_creds = *state->creds;
status = netlogon_creds_client_authenticator(&state->tmp_creds,
&state->req_auth);
if (tevent_req_nterror(req, status)) {
return;
}
ZERO_STRUCT(state->rep_auth);
subreq = dcerpc_netr_DsrUpdateReadOnlyServerDnsRecords_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->tmp_creds.computer_name,
&state->req_auth,
&state->rep_auth,
state->site_name,
state->dns_ttl,
state->dns_names);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
return;
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done,
req);
}
static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state);
NTSTATUS status;
NTSTATUS result;
bool ok;
/*
* We use state->dns_names as the memory context, as this is
* the only in/out variable and it has been overwritten by the
* out parameter from the server.
*
* We need to preserve the return value until the caller can use it.
*/
status = dcerpc_netr_DsrUpdateReadOnlyServerDnsRecords_recv(subreq, state->dns_names,
&result);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
return;
}
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_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
return;
}
*state->creds = state->tmp_creds;
status = netlogon_creds_cli_store(state->context,
state->creds);
TALLOC_FREE(state->creds);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
return;
}
if (tevent_req_nterror(req, result)) {
netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, result);
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_recv(struct tevent_req *req)
{
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status);
tevent_req_received(req);
return status;
}
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords(
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
const char *site_name,
uint32_t dns_ttl,
struct NL_DNS_NAME_INFO_ARRAY *dns_names)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_send(frame, ev, context, b,
site_name,
dns_ttl,
dns_names);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_recv(req);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_ServerGetTrustInfo_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
char *srv_name_slash;
enum dcerpc_AuthType auth_type;
enum dcerpc_AuthLevel auth_level;
struct samr_Password new_owf_password;
struct samr_Password old_owf_password;
struct netr_TrustInfo *trust_info;
struct netlogon_creds_CredentialState *creds;
struct netlogon_creds_CredentialState tmp_creds;
struct netr_Authenticator req_auth;
struct netr_Authenticator rep_auth;
};
static void netlogon_creds_cli_ServerGetTrustInfo_cleanup(struct tevent_req *req,
NTSTATUS status);
static void netlogon_creds_cli_ServerGetTrustInfo_locked(struct tevent_req *subreq);
struct tevent_req *netlogon_creds_cli_ServerGetTrustInfo_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b)
{
struct tevent_req *req;
struct netlogon_creds_cli_ServerGetTrustInfo_state *state;
struct tevent_req *subreq;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_ServerGetTrustInfo_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
dcerpc_binding_handle_auth_info(state->binding_handle,
&state->auth_type,
&state->auth_level);
subreq = netlogon_creds_cli_lock_send(state, state->ev,
state->context);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_ServerGetTrustInfo_locked,
req);
return req;
}
static void netlogon_creds_cli_ServerGetTrustInfo_cleanup(struct tevent_req *req,
NTSTATUS status)
{
struct netlogon_creds_cli_ServerGetTrustInfo_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_ServerGetTrustInfo_state);
if (state->creds == NULL) {
return;
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
!NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
TALLOC_FREE(state->creds);
return;
}
netlogon_creds_cli_delete(state->context, state->creds);
TALLOC_FREE(state->creds);
}
static void netlogon_creds_cli_ServerGetTrustInfo_done(struct tevent_req *subreq);
static void netlogon_creds_cli_ServerGetTrustInfo_locked(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_ServerGetTrustInfo_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_ServerGetTrustInfo_state);
NTSTATUS status;
status = netlogon_creds_cli_lock_recv(subreq, state,
&state->creds);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) {
switch (state->auth_level) {
case DCERPC_AUTH_LEVEL_PRIVACY:
break;
default:
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
return;
}
} else {
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
return;
}
/*
* we defer all callbacks in order to cleanup
* the database record.
*/
tevent_req_defer_callback(req, state->ev);
state->tmp_creds = *state->creds;
status = netlogon_creds_client_authenticator(&state->tmp_creds,
&state->req_auth);
if (tevent_req_nterror(req, status)) {
return;
}
ZERO_STRUCT(state->rep_auth);
subreq = dcerpc_netr_ServerGetTrustInfo_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->tmp_creds.account_name,
state->tmp_creds.secure_channel_type,
state->tmp_creds.computer_name,
&state->req_auth,
&state->rep_auth,
&state->new_owf_password,
&state->old_owf_password,
&state->trust_info);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
return;
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_ServerGetTrustInfo_done,
req);
}
static void netlogon_creds_cli_ServerGetTrustInfo_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_ServerGetTrustInfo_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_ServerGetTrustInfo_state);
NTSTATUS status;
NTSTATUS result;
const struct samr_Password zero = {};
bool cmp;
bool ok;
/*
* We use state->dns_names as the memory context, as this is
* the only in/out variable and it has been overwritten by the
* out parameter from the server.
*
* We need to preserve the return value until the caller can use it.
*/
status = dcerpc_netr_ServerGetTrustInfo_recv(subreq, state, &result);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
return;
}
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_ServerGetTrustInfo_cleanup(req, status);
return;
}
cmp = mem_equal_const_time(state->new_owf_password.hash,
zero.hash, sizeof(zero.hash));
if (!cmp) {
status = netlogon_creds_des_decrypt(&state->tmp_creds,
&state->new_owf_password);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
return;
}
}
cmp = mem_equal_const_time(state->old_owf_password.hash,
zero.hash, sizeof(zero.hash));
if (!cmp) {
status = netlogon_creds_des_decrypt(&state->tmp_creds,
&state->old_owf_password);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
return;
}
}
*state->creds = state->tmp_creds;
status = netlogon_creds_cli_store(state->context,
state->creds);
TALLOC_FREE(state->creds);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
return;
}
if (tevent_req_nterror(req, result)) {
netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, result);
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_ServerGetTrustInfo_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
struct samr_Password *new_owf_password,
struct samr_Password *old_owf_password,
struct netr_TrustInfo **trust_info)
{
struct netlogon_creds_cli_ServerGetTrustInfo_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_ServerGetTrustInfo_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status);
tevent_req_received(req);
return status;
}
if (new_owf_password != NULL) {
*new_owf_password = state->new_owf_password;
}
if (old_owf_password != NULL) {
*old_owf_password = state->old_owf_password;
}
if (trust_info != NULL) {
*trust_info = talloc_move(mem_ctx, &state->trust_info);
}
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_ServerGetTrustInfo(
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
TALLOC_CTX *mem_ctx,
struct samr_Password *new_owf_password,
struct samr_Password *old_owf_password,
struct netr_TrustInfo **trust_info)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_ServerGetTrustInfo_send(frame, ev, context, b);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_ServerGetTrustInfo_recv(req,
mem_ctx,
new_owf_password,
old_owf_password,
trust_info);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_GetForestTrustInformation_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
char *srv_name_slash;
enum dcerpc_AuthType auth_type;
enum dcerpc_AuthLevel auth_level;
uint32_t flags;
struct lsa_ForestTrustInformation *forest_trust_info;
struct netlogon_creds_CredentialState *creds;
struct netlogon_creds_CredentialState tmp_creds;
struct netr_Authenticator req_auth;
struct netr_Authenticator rep_auth;
};
static void netlogon_creds_cli_GetForestTrustInformation_cleanup(struct tevent_req *req,
NTSTATUS status);
static void netlogon_creds_cli_GetForestTrustInformation_locked(struct tevent_req *subreq);
struct tevent_req *netlogon_creds_cli_GetForestTrustInformation_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b)
{
struct tevent_req *req;
struct netlogon_creds_cli_GetForestTrustInformation_state *state;
struct tevent_req *subreq;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_GetForestTrustInformation_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
state->flags = 0;
dcerpc_binding_handle_auth_info(state->binding_handle,
&state->auth_type,
&state->auth_level);
subreq = netlogon_creds_cli_lock_send(state, state->ev,
state->context);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_GetForestTrustInformation_locked,
req);
return req;
}
static void netlogon_creds_cli_GetForestTrustInformation_cleanup(struct tevent_req *req,
NTSTATUS status)
{
struct netlogon_creds_cli_GetForestTrustInformation_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_GetForestTrustInformation_state);
if (state->creds == NULL) {
return;
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
!NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
TALLOC_FREE(state->creds);
return;
}
netlogon_creds_cli_delete(state->context, state->creds);
TALLOC_FREE(state->creds);
}
static void netlogon_creds_cli_GetForestTrustInformation_done(struct tevent_req *subreq);
static void netlogon_creds_cli_GetForestTrustInformation_locked(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_GetForestTrustInformation_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_GetForestTrustInformation_state);
NTSTATUS status;
status = netlogon_creds_cli_lock_recv(subreq, state,
&state->creds);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
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;
}
}
/*
* we defer all callbacks in order to cleanup
* the database record.
*/
tevent_req_defer_callback(req, state->ev);
state->tmp_creds = *state->creds;
status = netlogon_creds_client_authenticator(&state->tmp_creds,
&state->req_auth);
if (tevent_req_nterror(req, status)) {
return;
}
ZERO_STRUCT(state->rep_auth);
subreq = dcerpc_netr_GetForestTrustInformation_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->tmp_creds.computer_name,
&state->req_auth,
&state->rep_auth,
state->flags,
&state->forest_trust_info);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
return;
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_GetForestTrustInformation_done,
req);
}
static void netlogon_creds_cli_GetForestTrustInformation_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_GetForestTrustInformation_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_GetForestTrustInformation_state);
NTSTATUS status;
NTSTATUS result;
bool ok;
/*
* We use state->dns_names as the memory context, as this is
* the only in/out variable and it has been overwritten by the
* out parameter from the server.
*
* We need to preserve the return value until the caller can use it.
*/
status = dcerpc_netr_GetForestTrustInformation_recv(subreq, state, &result);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
return;
}
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_GetForestTrustInformation_cleanup(req, status);
return;
}
*state->creds = state->tmp_creds;
status = netlogon_creds_cli_store(state->context,
state->creds);
TALLOC_FREE(state->creds);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
return;
}
if (tevent_req_nterror(req, result)) {
netlogon_creds_cli_GetForestTrustInformation_cleanup(req, result);
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_GetForestTrustInformation_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
struct lsa_ForestTrustInformation **forest_trust_info)
{
struct netlogon_creds_cli_GetForestTrustInformation_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_GetForestTrustInformation_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status);
tevent_req_received(req);
return status;
}
*forest_trust_info = talloc_move(mem_ctx, &state->forest_trust_info);
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_GetForestTrustInformation(
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
TALLOC_CTX *mem_ctx,
struct lsa_ForestTrustInformation **forest_trust_info)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_GetForestTrustInformation_send(frame, ev, context, b);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_GetForestTrustInformation_recv(req,
mem_ctx,
forest_trust_info);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_SendToSam_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
char *srv_name_slash;
enum dcerpc_AuthType auth_type;
enum dcerpc_AuthLevel auth_level;
DATA_BLOB opaque;
struct netlogon_creds_CredentialState *creds;
struct netlogon_creds_CredentialState tmp_creds;
struct netr_Authenticator req_auth;
struct netr_Authenticator rep_auth;
};
static void netlogon_creds_cli_SendToSam_cleanup(struct tevent_req *req,
NTSTATUS status);
static void netlogon_creds_cli_SendToSam_locked(struct tevent_req *subreq);
struct tevent_req *netlogon_creds_cli_SendToSam_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
struct netr_SendToSamBase *message)
{
struct tevent_req *req;
struct netlogon_creds_cli_SendToSam_state *state;
struct tevent_req *subreq;
enum ndr_err_code ndr_err;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_SendToSam_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
ndr_err = ndr_push_struct_blob(&state->opaque, mem_ctx, message,
(ndr_push_flags_fn_t)ndr_push_netr_SendToSamBase);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
tevent_req_nterror(req, status);
return tevent_req_post(req, ev);
}
dcerpc_binding_handle_auth_info(state->binding_handle,
&state->auth_type,
&state->auth_level);
subreq = netlogon_creds_cli_lock_send(state, state->ev,
state->context);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_SendToSam_locked,
req);
return req;
}
static void netlogon_creds_cli_SendToSam_cleanup(struct tevent_req *req,
NTSTATUS status)
{
struct netlogon_creds_cli_SendToSam_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_SendToSam_state);
if (state->creds == NULL) {
return;
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
!NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
TALLOC_FREE(state->creds);
return;
}
netlogon_creds_cli_delete(state->context, state->creds);
TALLOC_FREE(state->creds);
}
static void netlogon_creds_cli_SendToSam_done(struct tevent_req *subreq);
static void netlogon_creds_cli_SendToSam_locked(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_SendToSam_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_SendToSam_state);
NTSTATUS status;
status = netlogon_creds_cli_lock_recv(subreq, state,
&state->creds);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
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;
}
}
/*
* we defer all callbacks in order to cleanup
* the database record.
*/
tevent_req_defer_callback(req, state->ev);
state->tmp_creds = *state->creds;
status = netlogon_creds_client_authenticator(&state->tmp_creds,
&state->req_auth);
if (tevent_req_nterror(req, status)) {
return;
}
ZERO_STRUCT(state->rep_auth);
if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
status = netlogon_creds_aes_encrypt(&state->tmp_creds,
state->opaque.data,
state->opaque.length);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_SendToSam_cleanup(req, status);
return;
}
} else {
status = netlogon_creds_arcfour_crypt(&state->tmp_creds,
state->opaque.data,
state->opaque.length);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_SendToSam_cleanup(req, status);
return;
}
}
subreq = dcerpc_netr_NetrLogonSendToSam_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->tmp_creds.computer_name,
&state->req_auth,
&state->rep_auth,
state->opaque.data,
state->opaque.length);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_SendToSam_cleanup(req, status);
return;
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_SendToSam_done,
req);
}
static void netlogon_creds_cli_SendToSam_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_SendToSam_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_SendToSam_state);
NTSTATUS status;
NTSTATUS result;
bool ok;
status = dcerpc_netr_NetrLogonSendToSam_recv(subreq, state, &result);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_SendToSam_cleanup(req, status);
return;
}
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_SendToSam_cleanup(req, status);
return;
}
*state->creds = state->tmp_creds;
status = netlogon_creds_cli_store(state->context,
state->creds);
TALLOC_FREE(state->creds);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_SendToSam_cleanup(req, status);
return;
}
/*
* Creds must be stored before we send back application errors
* e.g. NT_STATUS_NOT_IMPLEMENTED
*/
if (tevent_req_nterror(req, result)) {
netlogon_creds_cli_SendToSam_cleanup(req, result);
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_SendToSam_recv(struct tevent_req *req)
{
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
netlogon_creds_cli_SendToSam_cleanup(req, status);
tevent_req_received(req);
return status;
}
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_SendToSam(struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
struct netr_SendToSamBase *message)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_NO_MEMORY;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_SendToSam_send(frame, ev, context, b, message);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_SendToSam_recv(req);
fail:
TALLOC_FREE(frame);
return status;
}
struct netlogon_creds_cli_LogonGetDomainInfo_state {
struct tevent_context *ev;
struct netlogon_creds_cli_context *context;
struct dcerpc_binding_handle *binding_handle;
char *srv_name_slash;
enum dcerpc_AuthType auth_type;
enum dcerpc_AuthLevel auth_level;
uint32_t level;
union netr_WorkstationInfo *query;
union netr_DomainInfo *info;
struct netlogon_creds_CredentialState *creds;
struct netlogon_creds_CredentialState tmp_creds;
struct netr_Authenticator req_auth;
struct netr_Authenticator rep_auth;
};
static void netlogon_creds_cli_LogonGetDomainInfo_cleanup(struct tevent_req *req,
NTSTATUS status);
static void netlogon_creds_cli_LogonGetDomainInfo_locked(struct tevent_req *subreq);
struct tevent_req *netlogon_creds_cli_LogonGetDomainInfo_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
uint32_t level,
union netr_WorkstationInfo *query)
{
struct tevent_req *req;
struct netlogon_creds_cli_LogonGetDomainInfo_state *state;
struct tevent_req *subreq;
req = tevent_req_create(mem_ctx, &state,
struct netlogon_creds_cli_LogonGetDomainInfo_state);
if (req == NULL) {
return NULL;
}
state->ev = ev;
state->context = context;
state->binding_handle = b;
state->srv_name_slash = talloc_asprintf(state, "\\\\%s",
context->server.computer);
if (tevent_req_nomem(state->srv_name_slash, req)) {
return tevent_req_post(req, ev);
}
state->level = level;
state->query = query;
state->info = talloc_zero(state, union netr_DomainInfo);
if (tevent_req_nomem(state->info, req)) {
return tevent_req_post(req, ev);
}
dcerpc_binding_handle_auth_info(state->binding_handle,
&state->auth_type,
&state->auth_level);
subreq = netlogon_creds_cli_lock_send(state, state->ev,
state->context);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_LogonGetDomainInfo_locked,
req);
return req;
}
static void netlogon_creds_cli_LogonGetDomainInfo_cleanup(struct tevent_req *req,
NTSTATUS status)
{
struct netlogon_creds_cli_LogonGetDomainInfo_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_LogonGetDomainInfo_state);
if (state->creds == NULL) {
return;
}
if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) &&
!NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
!NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
TALLOC_FREE(state->creds);
return;
}
netlogon_creds_cli_delete(state->context, state->creds);
}
static void netlogon_creds_cli_LogonGetDomainInfo_done(struct tevent_req *subreq);
static void netlogon_creds_cli_LogonGetDomainInfo_locked(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_LogonGetDomainInfo_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_LogonGetDomainInfo_state);
NTSTATUS status;
status = netlogon_creds_cli_lock_recv(subreq, state,
&state->creds);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
return;
}
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;
}
}
/*
* we defer all callbacks in order to cleanup
* the database record.
*/
tevent_req_defer_callback(req, state->ev);
state->tmp_creds = *state->creds;
status = netlogon_creds_client_authenticator(&state->tmp_creds,
&state->req_auth);
if (tevent_req_nterror(req, status)) {
return;
}
ZERO_STRUCT(state->rep_auth);
subreq = dcerpc_netr_LogonGetDomainInfo_send(state, state->ev,
state->binding_handle,
state->srv_name_slash,
state->tmp_creds.computer_name,
&state->req_auth,
&state->rep_auth,
state->level,
state->query,
state->info);
if (tevent_req_nomem(subreq, req)) {
status = NT_STATUS_NO_MEMORY;
netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
return;
}
tevent_req_set_callback(subreq,
netlogon_creds_cli_LogonGetDomainInfo_done,
req);
}
static void netlogon_creds_cli_LogonGetDomainInfo_done(struct tevent_req *subreq)
{
struct tevent_req *req =
tevent_req_callback_data(subreq,
struct tevent_req);
struct netlogon_creds_cli_LogonGetDomainInfo_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_LogonGetDomainInfo_state);
NTSTATUS status;
NTSTATUS result;
bool ok;
/*
* We use state->dns_names as the memory context, as this is
* the only in/out variable and it has been overwritten by the
* out parameter from the server.
*
* We need to preserve the return value until the caller can use it.
*/
status = dcerpc_netr_LogonGetDomainInfo_recv(subreq, state->info, &result);
TALLOC_FREE(subreq);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
return;
}
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_LogonGetDomainInfo_cleanup(req, status);
return;
}
if (tevent_req_nterror(req, result)) {
netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, result);
return;
}
*state->creds = state->tmp_creds;
status = netlogon_creds_cli_store(state->context,
state->creds);
if (tevent_req_nterror(req, status)) {
netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
return;
}
tevent_req_done(req);
}
NTSTATUS netlogon_creds_cli_LogonGetDomainInfo_recv(struct tevent_req *req,
TALLOC_CTX *mem_ctx,
union netr_DomainInfo **info)
{
struct netlogon_creds_cli_LogonGetDomainInfo_state *state =
tevent_req_data(req,
struct netlogon_creds_cli_LogonGetDomainInfo_state);
NTSTATUS status;
if (tevent_req_is_nterror(req, &status)) {
netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status);
tevent_req_received(req);
return status;
}
*info = talloc_move(mem_ctx, &state->info);
tevent_req_received(req);
return NT_STATUS_OK;
}
NTSTATUS netlogon_creds_cli_LogonGetDomainInfo(
struct netlogon_creds_cli_context *context,
struct dcerpc_binding_handle *b,
TALLOC_CTX *mem_ctx,
uint32_t level,
union netr_WorkstationInfo *query,
union netr_DomainInfo **info)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
NTSTATUS status = NT_STATUS_OK;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
goto fail;
}
req = netlogon_creds_cli_LogonGetDomainInfo_send(frame, ev, context, b,
level, query);
if (req == NULL) {
goto fail;
}
if (!tevent_req_poll_ntstatus(req, ev, &status)) {
goto fail;
}
status = netlogon_creds_cli_LogonGetDomainInfo_recv(req,
mem_ctx,
info);
fail:
TALLOC_FREE(frame);
return status;
}