diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /source4/rpc_server/netlogon | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/rpc_server/netlogon')
-rw-r--r-- | source4/rpc_server/netlogon/dcerpc_netlogon.c | 4631 |
1 files changed, 4631 insertions, 0 deletions
diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c new file mode 100644 index 0000000..3f312f1 --- /dev/null +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -0,0 +1,4631 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the netlogon pipe + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2008 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2005 + Copyright (C) Matthias Dieter Wallnöfer 2009-2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "auth/auth.h" +#include "auth/auth_sam_reply.h" +#include "dsdb/samdb/samdb.h" +#include "../lib/util/util_ldb.h" +#include "../libcli/auth/schannel.h" +#include "libcli/security/security.h" +#include "param/param.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_irpc_c.h" +#include "../libcli/ldap/ldap_ndr.h" +#include "dsdb/samdb/ldb_modules/util.h" +#include "lib/tsocket/tsocket.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "librpc/gen_ndr/ndr_samr.h" +#include "librpc/gen_ndr/ndr_irpc.h" +#include "librpc/gen_ndr/ndr_winbind.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "librpc/rpc/server/netlogon/schannel_util.h" +#include "lib/socket/netif.h" +#include "lib/util/util_str_escape.h" +#include "lib/param/loadparm.h" + +#define DCESRV_INTERFACE_NETLOGON_BIND(context, iface) \ + dcesrv_interface_netlogon_bind(context, iface) + +#undef strcasecmp + +/* + * This #define allows the netlogon interface to accept invalid + * association groups, because association groups are to coordinate + * handles, and handles are not used in NETLOGON. This in turn avoids + * the need to coordinate these across multiple possible NETLOGON + * processes + */ +#define DCESRV_INTERFACE_NETLOGON_FLAGS DCESRV_INTERFACE_FLAGS_HANDLES_NOT_USED + +static NTSTATUS dcesrv_interface_netlogon_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + struct loadparm_context *lp_ctx = context->conn->dce_ctx->lp_ctx; + bool global_allow_nt4_crypto = lpcfg_allow_nt4_crypto(lp_ctx); + bool global_reject_md5_client = lpcfg_reject_md5_clients(lp_ctx); + int schannel = lpcfg_server_schannel(lp_ctx); + bool schannel_global_required = (schannel == true); + bool global_require_seal = lpcfg_server_schannel_require_seal(lp_ctx); + static bool warned_global_nt4_once = false; + static bool warned_global_md5_once = false; + static bool warned_global_schannel_once = false; + static bool warned_global_seal_once = false; + + if (global_allow_nt4_crypto && !warned_global_nt4_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2022-38023 (and others): " + "Please configure 'allow nt4 crypto = no' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_nt4_once = true; + } + + if (!global_reject_md5_client && !warned_global_md5_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2022-38023: " + "Please configure 'reject md5 clients = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_md5_once = true; + } + + if (!schannel_global_required && !warned_global_schannel_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2020-1472(ZeroLogon): " + "Please configure 'server schannel = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=14497\n"); + warned_global_schannel_once = true; + } + + if (!global_require_seal && !warned_global_seal_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2022-38023 (and others): " + "Please configure 'server schannel require seal = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_seal_once = true; + } + + return dcesrv_interface_bind_reject_connect(context, iface); +} + +static NTSTATUS dcesrv_netr_ServerReqChallenge(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerReqChallenge *r) +{ + struct netlogon_server_pipe_state *pipe_state = NULL; + NTSTATUS ntstatus; + + ZERO_STRUCTP(r->out.return_credentials); + + pipe_state = dcesrv_iface_state_find_conn(dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + struct netlogon_server_pipe_state); + TALLOC_FREE(pipe_state); + + pipe_state = talloc_zero(dce_call, + struct netlogon_server_pipe_state); + if (pipe_state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + pipe_state->client_challenge = *r->in.credentials; + + netlogon_creds_random_challenge(&pipe_state->server_challenge); + + *r->out.return_credentials = pipe_state->server_challenge; + + ntstatus = dcesrv_iface_state_store_conn(dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + pipe_state); + if (!NT_STATUS_IS_OK(ntstatus)) { + return ntstatus; + } + + ntstatus = schannel_save_challenge(dce_call->conn->dce_ctx->lp_ctx, + &pipe_state->client_challenge, + &pipe_state->server_challenge, + r->in.computer_name); + if (!NT_STATUS_IS_OK(ntstatus)) { + TALLOC_FREE(pipe_state); + return ntstatus; + } + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_netr_ServerAuthenticate3_check_downgrade( + struct dcesrv_call_state *dce_call, + struct netr_ServerAuthenticate3 *r, + struct netlogon_server_pipe_state *pipe_state, + uint32_t negotiate_flags, + const char *trust_account_in_db, + NTSTATUS orig_status) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + bool global_allow_nt4_crypto = lpcfg_allow_nt4_crypto(lp_ctx); + bool account_allow_nt4_crypto = global_allow_nt4_crypto; + const char *explicit_nt4_opt = NULL; + bool global_reject_md5_client = lpcfg_reject_md5_clients(lp_ctx); + bool account_reject_md5_client = global_reject_md5_client; + const char *explicit_md5_opt = NULL; + bool reject_des_client; + bool allow_nt4_crypto; + bool reject_md5_client; + bool need_des = true; + bool need_md5 = true; + int CVE_2022_38023_warn_level = lpcfg_parm_int(lp_ctx, NULL, + "CVE_2022_38023", "warn_about_unused_debug_level", DBGLVL_ERR); + int CVE_2022_38023_error_level = lpcfg_parm_int(lp_ctx, NULL, + "CVE_2022_38023", "error_debug_level", DBGLVL_ERR); + + /* + * We don't use lpcfg_parm_bool(), as we + * need the explicit_opt pointer in order to + * adjust the debug messages. + */ + + if (trust_account_in_db != NULL) { + explicit_nt4_opt = lpcfg_get_parametric(lp_ctx, + NULL, + "allow nt4 crypto", + trust_account_in_db); + } + if (explicit_nt4_opt != NULL) { + account_allow_nt4_crypto = lp_bool(explicit_nt4_opt); + } + allow_nt4_crypto = account_allow_nt4_crypto; + if (trust_account_in_db != NULL) { + explicit_md5_opt = lpcfg_get_parametric(lp_ctx, + NULL, + "server reject md5 schannel", + trust_account_in_db); + } + if (explicit_md5_opt != NULL) { + account_reject_md5_client = lp_bool(explicit_md5_opt); + } + reject_md5_client = account_reject_md5_client; + + reject_des_client = !allow_nt4_crypto; + + /* + * If weak cryto is disabled, do not announce that we support RC4. + */ + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED) { + /* Without RC4 and DES we require AES */ + reject_des_client = true; + reject_md5_client = true; + } + + if (negotiate_flags & NETLOGON_NEG_STRONG_KEYS) { + need_des = false; + reject_des_client = false; + } + + if (negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + need_des = false; + need_md5 = false; + reject_des_client = false; + reject_md5_client = false; + } + + if (reject_des_client || reject_md5_client) { + TALLOC_CTX *frame = talloc_stackframe(); + + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED) { + if (CVE_2022_38023_error_level < DBGLVL_NOTICE) { + CVE_2022_38023_error_level = DBGLVL_NOTICE; + } + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: " + "client_account[%s] computer_name[%s] " + "schannel_type[%u] " + "client_negotiate_flags[0x%x] " + "%s%s%s " + "NT_STATUS_DOWNGRADE_DETECTED " + "WEAK_CRYPTO_DISALLOWED\n", + log_escape(frame, r->in.account_name), + log_escape(frame, r->in.computer_name), + r->in.secure_channel_type, + (unsigned)*r->in.negotiate_flags, + trust_account_in_db ? "real_account[" : "", + trust_account_in_db ? trust_account_in_db : "", + trust_account_in_db ? "]" : "")); + goto return_downgrade; + } + + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: " + "client_account[%s] computer_name[%s] " + "schannel_type[%u] " + "client_negotiate_flags[0x%x] " + "%s%s%s " + "NT_STATUS_DOWNGRADE_DETECTED " + "reject_des[%u] reject_md5[%u]\n", + log_escape(frame, r->in.account_name), + log_escape(frame, r->in.computer_name), + r->in.secure_channel_type, + (unsigned)*r->in.negotiate_flags, + trust_account_in_db ? "real_account[" : "", + trust_account_in_db ? trust_account_in_db : "", + trust_account_in_db ? "]" : "", + reject_des_client, + reject_md5_client)); + if (trust_account_in_db == NULL) { + goto return_downgrade; + } + + if (reject_md5_client && explicit_md5_opt == NULL) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = no' " + "might be needed for a legacy client.\n", + trust_account_in_db)); + } + if (reject_des_client && explicit_nt4_opt == NULL) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = yes' " + "might be needed for a legacy client.\n", + trust_account_in_db)); + } + +return_downgrade: + /* + * Here we match Windows 2012 and return no flags. + */ + *r->out.negotiate_flags = 0; + TALLOC_FREE(frame); + return NT_STATUS_DOWNGRADE_DETECTED; + } + + /* + * This talloc_free is important to prevent re-use of the + * challenge. We have to delay it this far due to NETApp + * servers per: + * https://bugzilla.samba.org/show_bug.cgi?id=11291 + */ + TALLOC_FREE(pipe_state); + + /* + * At this point we must also cleanup the TDB cache + * entry, if we fail the client needs to call + * netr_ServerReqChallenge again. + * + * Note: this handles a non existing record just fine, + * the r->in.computer_name might not be the one used + * in netr_ServerReqChallenge(), but we are trying to + * just tidy up the normal case to prevent re-use. + */ + schannel_delete_challenge(dce_call->conn->dce_ctx->lp_ctx, + r->in.computer_name); + + /* + * According to Microsoft (see bugid #6099) + * Windows 7 looks at the negotiate_flags + * returned in this structure *even if the + * call fails with access denied! + */ + *r->out.negotiate_flags = negotiate_flags; + + if (!NT_STATUS_IS_OK(orig_status) || trust_account_in_db == NULL) { + return orig_status; + } + + if (global_reject_md5_client && account_reject_md5_client && explicit_md5_opt) { + D_INFO("CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = yes' not needed!?\n", + trust_account_in_db); + } else if (need_md5 && !account_reject_md5_client && explicit_md5_opt) { + D_INFO("CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = no' " + "still needed for a legacy client.\n", + trust_account_in_db); + } else if (need_md5 && explicit_md5_opt == NULL) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = no' " + "might be needed for a legacy client.\n", + trust_account_in_db)); + } else if (!account_reject_md5_client && explicit_md5_opt) { + DEBUG(CVE_2022_38023_warn_level, ( + "CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = no' not needed!?\n", + trust_account_in_db)); + } + + if (!global_allow_nt4_crypto && !account_allow_nt4_crypto && explicit_nt4_opt) { + D_INFO("CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = no' not needed!?\n", + trust_account_in_db); + } else if (need_des && account_allow_nt4_crypto && explicit_nt4_opt) { + D_INFO("CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = yes' " + "still needed for a legacy client.\n", + trust_account_in_db); + } else if (need_des && explicit_nt4_opt == NULL) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = yes' " + "might be needed for a legacy client.\n", + trust_account_in_db)); + } else if (account_allow_nt4_crypto && explicit_nt4_opt) { + DEBUG(CVE_2022_38023_warn_level, ( + "CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = yes' not needed!?\n", + trust_account_in_db)); + } + + return orig_status; +} + +/* + * Do the actual processing of a netr_ServerAuthenticate3 message. + * called from dcesrv_netr_ServerAuthenticate3, which handles the logging. + */ +static NTSTATUS dcesrv_netr_ServerAuthenticate3_helper( + struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_ServerAuthenticate3 *r, + const char **trust_account_for_search, + const char **trust_account_in_db, + struct dom_sid **sid) +{ + struct netlogon_server_pipe_state *pipe_state = NULL; + bool challenge_valid = false; + struct netlogon_server_pipe_state challenge; + struct netlogon_creds_CredentialState *creds; + struct ldb_context *sam_ctx; + struct samr_Password *curNtHash = NULL; + struct samr_Password *prevNtHash = NULL; + uint32_t user_account_control; + int num_records; + struct ldb_message **msgs; + NTSTATUS nt_status; + const char *attrs[] = {"unicodePwd", "userAccountControl", + "objectSid", "samAccountName", NULL}; + uint32_t server_flags = 0; + uint32_t negotiate_flags = 0; + + ZERO_STRUCTP(r->out.return_credentials); + *r->out.negotiate_flags = 0; + *r->out.rid = 0; + + pipe_state = dcesrv_iface_state_find_conn(dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + struct netlogon_server_pipe_state); + if (pipe_state != NULL) { + /* + * If we had a challenge remembered on the connection + * consider this for usage. This can't be cleanup + * by other clients. + * + * This is the default code path for typical clients + * which call netr_ServerReqChallenge() and + * netr_ServerAuthenticate3() on the same dcerpc connection. + */ + challenge = *pipe_state; + + challenge_valid = true; + + } else { + NTSTATUS ntstatus; + + /* + * Fallback and try to get the challenge from + * the global cache. + * + * If too many clients are using this code path, + * they may destroy their cache entries as the + * TDB has a fixed size limited via a lossy hash + * + * The TDB used is the schannel store, which is + * initialised at startup. + * + * NOTE: The challenge is deleted from the DB as soon as it is + * fetched, to prevent reuse. + * + */ + + ntstatus = schannel_get_challenge(dce_call->conn->dce_ctx->lp_ctx, + &challenge.client_challenge, + &challenge.server_challenge, + r->in.computer_name); + + if (!NT_STATUS_IS_OK(ntstatus)) { + ZERO_STRUCT(challenge); + } else { + challenge_valid = true; + } + } + + server_flags = NETLOGON_NEG_ACCOUNT_LOCKOUT | + NETLOGON_NEG_PERSISTENT_SAMREPL | + NETLOGON_NEG_ARCFOUR | + NETLOGON_NEG_PROMOTION_COUNT | + NETLOGON_NEG_CHANGELOG_BDC | + NETLOGON_NEG_FULL_SYNC_REPL | + NETLOGON_NEG_MULTIPLE_SIDS | + NETLOGON_NEG_REDO | + NETLOGON_NEG_PASSWORD_CHANGE_REFUSAL | + NETLOGON_NEG_SEND_PASSWORD_INFO_PDC | + NETLOGON_NEG_GENERIC_PASSTHROUGH | + NETLOGON_NEG_CONCURRENT_RPC | + NETLOGON_NEG_AVOID_ACCOUNT_DB_REPL | + NETLOGON_NEG_AVOID_SECURITYAUTH_DB_REPL | + NETLOGON_NEG_STRONG_KEYS | + NETLOGON_NEG_TRANSITIVE_TRUSTS | + NETLOGON_NEG_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_PASSWORD_SET2 | + NETLOGON_NEG_GETDOMAININFO | + NETLOGON_NEG_CROSS_FOREST_TRUSTS | + NETLOGON_NEG_NEUTRALIZE_NT4_EMULATION | + NETLOGON_NEG_RODC_PASSTHROUGH | + NETLOGON_NEG_SUPPORTS_AES | + NETLOGON_NEG_AUTHENTICATED_RPC_LSASS | + NETLOGON_NEG_AUTHENTICATED_RPC; + + /* + * If weak cryto is disabled, do not announce that we support RC4. + */ + if (lpcfg_weak_crypto(dce_call->conn->dce_ctx->lp_ctx) == + SAMBA_WEAK_CRYPTO_DISALLOWED) { + server_flags &= ~NETLOGON_NEG_ARCFOUR; + } + + negotiate_flags = *r->in.negotiate_flags & server_flags; + + switch (r->in.secure_channel_type) { + case SEC_CHAN_WKSTA: + case SEC_CHAN_DNS_DOMAIN: + case SEC_CHAN_DOMAIN: + case SEC_CHAN_BDC: + case SEC_CHAN_RODC: + break; + case SEC_CHAN_NULL: + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INVALID_PARAMETER); + default: + DEBUG(1, ("Client asked for an invalid secure channel type: %d\n", + r->in.secure_channel_type)); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INVALID_PARAMETER); + } + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INVALID_SYSTEM_SERVICE); + } + + if (r->in.secure_channel_type == SEC_CHAN_DOMAIN || + r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) + { + struct ldb_message *tdo_msg = NULL; + const char * const tdo_attrs[] = { + "trustAuthIncoming", + "trustAttributes", + "flatName", + NULL + }; + char *encoded_name = NULL; + size_t len; + const char *flatname = NULL; + char trailer = '$'; + bool require_trailer = true; + const char *netbios = NULL; + const char *dns = NULL; + + if (r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + trailer = '.'; + require_trailer = false; + } + + encoded_name = ldb_binary_encode_string(mem_ctx, + r->in.account_name); + if (encoded_name == NULL) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_MEMORY); + } + + len = strlen(encoded_name); + if (len < 2) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + + if (require_trailer && encoded_name[len - 1] != trailer) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + encoded_name[len - 1] = '\0'; + + if (r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + dns = encoded_name; + } else { + netbios = encoded_name; + } + + nt_status = dsdb_trust_search_tdo(sam_ctx, + netbios, dns, + tdo_attrs, mem_ctx, &tdo_msg); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + DEBUG(2, ("Client asked for a trusted domain secure channel, " + "but there's no tdo for [%s] => [%s] \n", + log_escape(mem_ctx, r->in.account_name), + encoded_name)); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + if (!NT_STATUS_IS_OK(nt_status)) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + nt_status); + } + + nt_status = dsdb_trust_get_incoming_passwords(tdo_msg, mem_ctx, + &curNtHash, + &prevNtHash); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED)) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + if (!NT_STATUS_IS_OK(nt_status)) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + nt_status); + } + + flatname = ldb_msg_find_attr_as_string(tdo_msg, "flatName", NULL); + if (flatname == NULL) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + + *trust_account_for_search = talloc_asprintf(mem_ctx, "%s$", flatname); + if (*trust_account_for_search == NULL) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_MEMORY); + } + } else { + *trust_account_for_search = r->in.account_name; + } + + /* pull the user attributes */ + num_records = gendb_search(sam_ctx, mem_ctx, NULL, &msgs, attrs, + "(&(sAMAccountName=%s)(objectclass=user))", + ldb_binary_encode_string(mem_ctx, + *trust_account_for_search)); + + if (num_records == 0) { + DEBUG(3,("Couldn't find user [%s] in samdb.\n", + log_escape(mem_ctx, r->in.account_name))); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + + if (num_records > 1) { + DEBUG(0,("Found %d records matching user [%s]\n", + num_records, + log_escape(mem_ctx, r->in.account_name))); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INTERNAL_DB_CORRUPTION); + } + + *trust_account_in_db = ldb_msg_find_attr_as_string(msgs[0], + "samAccountName", + NULL); + if (*trust_account_in_db == NULL) { + DEBUG(0,("No samAccountName returned in record matching user [%s]\n", + r->in.account_name)); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INTERNAL_DB_CORRUPTION); + } + + nt_status = dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + *trust_account_in_db, + NT_STATUS_OK); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + user_account_control = ldb_msg_find_attr_as_uint(msgs[0], "userAccountControl", 0); + + if (user_account_control & UF_ACCOUNTDISABLE) { + DEBUG(1, ("Account [%s] is disabled\n", + log_escape(mem_ctx, r->in.account_name))); + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + + if (r->in.secure_channel_type == SEC_CHAN_WKSTA) { + if (!(user_account_control & UF_WORKSTATION_TRUST_ACCOUNT)) { + DEBUG(1, ("Client asked for a workstation secure channel, but is not a workstation (member server) acb flags: 0x%x\n", user_account_control)); + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + } else if (r->in.secure_channel_type == SEC_CHAN_DOMAIN || + r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + if (!(user_account_control & UF_INTERDOMAIN_TRUST_ACCOUNT)) { + DEBUG(1, ("Client asked for a trusted domain secure channel, but is not a trusted domain: acb flags: 0x%x\n", user_account_control)); + + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + } else if (r->in.secure_channel_type == SEC_CHAN_BDC) { + if (!(user_account_control & UF_SERVER_TRUST_ACCOUNT)) { + DEBUG(1, ("Client asked for a server secure channel, but is not a server (domain controller): acb flags: 0x%x\n", user_account_control)); + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + } else if (r->in.secure_channel_type == SEC_CHAN_RODC) { + if (!(user_account_control & UF_PARTIAL_SECRETS_ACCOUNT)) { + DEBUG(1, ("Client asked for a RODC secure channel, but is not a RODC: acb flags: 0x%x\n", user_account_control)); + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + } else { + /* we should never reach this */ + return NT_STATUS_INTERNAL_ERROR; + } + + if (!(user_account_control & UF_INTERDOMAIN_TRUST_ACCOUNT)) { + nt_status = samdb_result_passwords_no_lockout(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + msgs[0], &curNtHash); + if (!NT_STATUS_IS_OK(nt_status)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + if (curNtHash == NULL) { + return NT_STATUS_ACCESS_DENIED; + } + + if (!challenge_valid) { + DEBUG(1, ("No challenge requested by client [%s/%s], " + "cannot authenticate\n", + log_escape(mem_ctx, r->in.computer_name), + log_escape(mem_ctx, r->in.account_name))); + return NT_STATUS_ACCESS_DENIED; + } + + creds = netlogon_creds_server_init(mem_ctx, + r->in.account_name, + r->in.computer_name, + r->in.secure_channel_type, + &challenge.client_challenge, + &challenge.server_challenge, + curNtHash, + r->in.credentials, + r->out.return_credentials, + negotiate_flags); + if (creds == NULL && prevNtHash != NULL) { + /* + * We fallback to the previous password for domain trusts. + * + * Note that lpcfg_old_password_allowed_period() doesn't + * apply here. + */ + creds = netlogon_creds_server_init(mem_ctx, + r->in.account_name, + r->in.computer_name, + r->in.secure_channel_type, + &challenge.client_challenge, + &challenge.server_challenge, + prevNtHash, + r->in.credentials, + r->out.return_credentials, + negotiate_flags); + } + + if (creds == NULL) { + return NT_STATUS_ACCESS_DENIED; + } + creds->sid = samdb_result_dom_sid(creds, msgs[0], "objectSid"); + *sid = talloc_memdup(mem_ctx, creds->sid, sizeof(struct dom_sid)); + + nt_status = schannel_save_creds_state(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + creds); + if (!NT_STATUS_IS_OK(nt_status)) { + ZERO_STRUCTP(r->out.return_credentials); + return nt_status; + } + + *r->out.rid = samdb_result_rid_from_sid(mem_ctx, msgs[0], + "objectSid", 0); + + return NT_STATUS_OK; +} + +/* + * Log a netr_ServerAuthenticate3 request, and then invoke + * dcesrv_netr_ServerAuthenticate3_helper to perform the actual processing + */ +static NTSTATUS dcesrv_netr_ServerAuthenticate3( + struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_ServerAuthenticate3 *r) +{ + NTSTATUS status; + struct dom_sid *sid = NULL; + const char *trust_account_for_search = NULL; + const char *trust_account_in_db = NULL; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + struct auth_usersupplied_info ui = { + .local_host = dce_call->conn->local_address, + .remote_host = dce_call->conn->remote_address, + .client = { + .account_name = r->in.account_name, + .domain_name = lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx), + }, + .service_description = "NETLOGON", + .auth_description = "ServerAuthenticate", + .netlogon_trust_account = { + .computer_name = r->in.computer_name, + .negotiate_flags = *r->in.negotiate_flags, + .secure_channel_type = r->in.secure_channel_type, + }, + }; + + status = dcesrv_netr_ServerAuthenticate3_helper(dce_call, + mem_ctx, + r, + &trust_account_for_search, + &trust_account_in_db, + &sid); + ui.netlogon_trust_account.sid = sid; + ui.netlogon_trust_account.account_name = trust_account_in_db; + ui.mapped.account_name = trust_account_for_search; + log_authentication_event( + imsg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + NULL, + &ui, + status, + lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx), + trust_account_in_db, + sid); + + return status; +} +static NTSTATUS dcesrv_netr_ServerAuthenticate(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerAuthenticate *r) +{ + struct netr_ServerAuthenticate3 a; + uint32_t rid; + /* TODO: + * negotiate_flags is used as an [in] parameter + * so it need to be initialised. + * + * (I think ... = 0; seems wrong here --metze) + */ + uint32_t negotiate_flags_in = 0; + uint32_t negotiate_flags_out = 0; + + a.in.server_name = r->in.server_name; + a.in.account_name = r->in.account_name; + a.in.secure_channel_type = r->in.secure_channel_type; + a.in.computer_name = r->in.computer_name; + a.in.credentials = r->in.credentials; + a.in.negotiate_flags = &negotiate_flags_in; + + a.out.return_credentials = r->out.return_credentials; + a.out.rid = &rid; + a.out.negotiate_flags = &negotiate_flags_out; + + return dcesrv_netr_ServerAuthenticate3(dce_call, mem_ctx, &a); +} + +static NTSTATUS dcesrv_netr_ServerAuthenticate2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerAuthenticate2 *r) +{ + struct netr_ServerAuthenticate3 r3; + uint32_t rid = 0; + + r3.in.server_name = r->in.server_name; + r3.in.account_name = r->in.account_name; + r3.in.secure_channel_type = r->in.secure_channel_type; + r3.in.computer_name = r->in.computer_name; + r3.in.credentials = r->in.credentials; + r3.out.return_credentials = r->out.return_credentials; + r3.in.negotiate_flags = r->in.negotiate_flags; + r3.out.negotiate_flags = r->out.negotiate_flags; + r3.out.rid = &rid; + + return dcesrv_netr_ServerAuthenticate3(dce_call, mem_ctx, &r3); +} + +/* + Change the machine account password for the currently connected + client. Supplies only the NT#. +*/ + +static NTSTATUS dcesrv_netr_ServerPasswordSet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerPasswordSet *r) +{ + struct netlogon_creds_CredentialState *creds; + struct ldb_context *sam_ctx; + NTSTATUS nt_status; + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, r->out.return_authenticator, + &creds); + NT_STATUS_NOT_OK_RETURN(nt_status); + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + nt_status = netlogon_creds_des_decrypt(creds, r->in.new_password); + NT_STATUS_NOT_OK_RETURN(nt_status); + + /* Using the sid for the account as the key, set the password */ + nt_status = samdb_set_password_sid(sam_ctx, mem_ctx, + creds->sid, + NULL, /* Don't have version */ + NULL, /* Don't have plaintext */ + r->in.new_password, + DSDB_PASSWORD_CHECKED_AND_CORRECT, /* Password change */ + NULL, NULL); + return nt_status; +} + +/* + Change the machine account password for the currently connected + client. Supplies new plaintext. +*/ +static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerPasswordSet2 *r) +{ + struct netlogon_creds_CredentialState *creds; + struct ldb_context *sam_ctx; + struct NL_PASSWORD_VERSION version = {}; + const uint32_t *new_version = NULL; + NTSTATUS nt_status; + DATA_BLOB new_password = data_blob_null; + size_t confounder_len; + DATA_BLOB dec_blob = data_blob_null; + DATA_BLOB enc_blob = data_blob_null; + struct samr_CryptPassword password_buf; + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, r->out.return_authenticator, + &creds); + NT_STATUS_NOT_OK_RETURN(nt_status); + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + memcpy(password_buf.data, r->in.new_password->data, 512); + SIVAL(password_buf.data, 512, r->in.new_password->length); + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + nt_status = netlogon_creds_aes_decrypt(creds, + password_buf.data, + 516); + } else { + nt_status = netlogon_creds_arcfour_crypt(creds, + password_buf.data, + 516); + } + + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + switch (creds->secure_channel_type) { + case SEC_CHAN_DOMAIN: + case SEC_CHAN_DNS_DOMAIN: { + uint32_t len = IVAL(password_buf.data, 512); + if (len <= 500) { + uint32_t ofs = 500 - len; + uint8_t *p; + + p = password_buf.data + ofs; + + version.ReservedField = IVAL(p, 0); + version.PasswordVersionNumber = IVAL(p, 4); + version.PasswordVersionPresent = IVAL(p, 8); + + if (version.PasswordVersionPresent == NETLOGON_PASSWORD_VERSION_NUMBER_PRESENT) { + new_version = &version.PasswordVersionNumber; + } + }} + break; + default: + break; + } + + if (!extract_pw_from_buffer(mem_ctx, password_buf.data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Make sure the length field was encrypted, + * otherwise we are under attack. + */ + if (new_password.length == r->in.new_password->length) { + DBG_WARNING("Length[%zu] field not encrypted\n", + new_password.length); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * We don't allow empty passwords for machine accounts. + */ + if (new_password.length < 2) { + DBG_WARNING("Empty password Length[%zu]\n", + new_password.length); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Make sure the confounder part of CryptPassword + * buffer was encrypted, otherwise we are under attack. + */ + confounder_len = 512 - new_password.length; + enc_blob = data_blob_const(r->in.new_password->data, confounder_len); + dec_blob = data_blob_const(password_buf.data, confounder_len); + if (confounder_len > 0 && data_blob_equal_const_time(&dec_blob, &enc_blob)) { + DBG_WARNING("Confounder buffer not encrypted Length[%zu]\n", + confounder_len); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Check that the password part was actually encrypted, + * otherwise we are under attack. + */ + enc_blob = data_blob_const(r->in.new_password->data + confounder_len, + new_password.length); + dec_blob = data_blob_const(password_buf.data + confounder_len, + new_password.length); + if (data_blob_equal_const_time(&dec_blob, &enc_blob)) { + DBG_WARNING("Password buffer not encrypted Length[%zu]\n", + new_password.length); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * don't allow zero buffers + */ + if (all_zero(new_password.data, new_password.length)) { + DBG_WARNING("Password zero buffer Length[%zu]\n", + new_password.length); + return NT_STATUS_WRONG_PASSWORD; + } + + /* Using the sid for the account as the key, set the password */ + nt_status = samdb_set_password_sid(sam_ctx, mem_ctx, + creds->sid, + new_version, + &new_password, /* we have plaintext */ + NULL, + DSDB_PASSWORD_CHECKED_AND_CORRECT, /* Password change */ + NULL, NULL); + return nt_status; +} + + +/* + netr_LogonUasLogon +*/ +static WERROR dcesrv_netr_LogonUasLogon(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonUasLogon *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_LogonUasLogoff +*/ +static WERROR dcesrv_netr_LogonUasLogoff(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonUasLogoff *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +static NTSTATUS dcesrv_netr_LogonSamLogon_check(struct dcesrv_call_state *dce_call, + const struct netr_LogonSamLogonEx *r) +{ + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + if (r->in.logon->password == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.validation_level) { + case NetlogonValidationSamInfo: /* 2 */ + case NetlogonValidationSamInfo2: /* 3 */ + case NetlogonValidationSamInfo4: /* 6 */ + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + if (r->in.logon->network == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.validation_level) { + case NetlogonValidationSamInfo: /* 2 */ + case NetlogonValidationSamInfo2: /* 3 */ + case NetlogonValidationSamInfo4: /* 6 */ + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; + + case NetlogonGenericInformation: + if (r->in.logon->generic == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.validation_level) { + /* TODO: case NetlogonValidationGenericInfo: 4 */ + case NetlogonValidationGenericInfo2: /* 5 */ + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + dcesrv_call_auth_info(dce_call, NULL, &auth_level); + + switch (r->in.validation_level) { + case NetlogonValidationSamInfo4: /* 6 */ + if (auth_level < DCERPC_AUTH_LEVEL_PRIVACY) { + return NT_STATUS_INVALID_PARAMETER; + } + break; + + default: + break; + } + + return NT_STATUS_OK; +} + +struct dcesrv_netr_LogonSamLogon_base_state { + struct dcesrv_call_state *dce_call; + + TALLOC_CTX *mem_ctx; + + struct netlogon_creds_CredentialState *creds; + + struct netr_LogonSamLogonEx r; + + uint32_t _ignored_flags; + + struct { + struct netr_LogonSamLogon *lsl; + struct netr_LogonSamLogonWithFlags *lslwf; + struct netr_LogonSamLogonEx *lslex; + } _r; + + struct kdc_check_generic_kerberos kr; +}; + +static void dcesrv_netr_LogonSamLogon_base_auth_done(struct tevent_req *subreq); +static void dcesrv_netr_LogonSamLogon_base_krb5_done(struct tevent_req *subreq); +static void dcesrv_netr_LogonSamLogon_base_reply( + struct dcesrv_netr_LogonSamLogon_base_state *state); + +/* + netr_LogonSamLogon_base + + This version of the function allows other wrappers to say 'do not check the credentials' + + We can't do the traditional 'wrapping' format completely, as this + function must only run under schannel +*/ +static NTSTATUS dcesrv_netr_LogonSamLogon_base_call(struct dcesrv_netr_LogonSamLogon_base_state *state) +{ + struct dcesrv_call_state *dce_call = state->dce_call; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + TALLOC_CTX *mem_ctx = state->mem_ctx; + struct netr_LogonSamLogonEx *r = &state->r; + struct netlogon_creds_CredentialState *creds = state->creds; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + const char *workgroup = lpcfg_workgroup(lp_ctx); + struct auth4_context *auth_context = NULL; + struct auth_usersupplied_info *user_info = NULL; + NTSTATUS nt_status; + struct tevent_req *subreq = NULL; + enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + dcesrv_call_auth_info(dce_call, &auth_type, &auth_level); + + switch (dce_call->pkt.u.request.opnum) { + case NDR_NETR_LOGONSAMLOGON: + case NDR_NETR_LOGONSAMLOGONWITHFLAGS: + /* + * These already called dcesrv_netr_check_schannel() + * via dcesrv_netr_creds_server_step_check() + */ + break; + case NDR_NETR_LOGONSAMLOGONEX: + default: + if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) { + return NT_STATUS_ACCESS_DENIED; + } + + nt_status = dcesrv_netr_check_schannel(dce_call, + creds, + auth_type, + auth_level, + dce_call->pkt.u.request.opnum); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + break; + } + + *r->out.authoritative = 1; + + if (*r->in.flags & NETLOGON_SAMLOGON_FLAG_PASS_TO_FOREST_ROOT) { + /* + * Currently we're always the forest root ourself. + */ + return NT_STATUS_NO_SUCH_USER; + } + + if (*r->in.flags & NETLOGON_SAMLOGON_FLAG_PASS_CROSS_FOREST_HOP) { + /* + * Currently we don't support trusts correctly yet. + */ + return NT_STATUS_NO_SUCH_USER; + } + + user_info = talloc_zero(mem_ctx, struct auth_usersupplied_info); + NT_STATUS_HAVE_NO_MEMORY(user_info); + + user_info->service_description = "SamLogon"; + + nt_status = netlogon_creds_decrypt_samlogon_logon(creds, + r->in.logon_level, + r->in.logon); + NT_STATUS_NOT_OK_RETURN(nt_status); + + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + + nt_status = auth_context_create_for_netlogon(mem_ctx, + dce_call->event_ctx, + imsg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + &auth_context); + NT_STATUS_NOT_OK_RETURN(nt_status); + + user_info->remote_host = dce_call->conn->remote_address; + user_info->local_host = dce_call->conn->local_address; + + user_info->netlogon_trust_account.secure_channel_type + = creds->secure_channel_type; + user_info->netlogon_trust_account.negotiate_flags + = creds->negotiate_flags; + + /* + * These two can be unrelated when the account is + * actually that of a trusted domain, so we want to + * know which DC in that trusted domain contacted + * us + */ + user_info->netlogon_trust_account.computer_name + = creds->computer_name; + user_info->netlogon_trust_account.account_name + = creds->account_name; + user_info->netlogon_trust_account.sid + = creds->sid; + + break; + default: + /* We do not need to set up the user_info in this case */ + break; + } + + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + user_info->auth_description = "interactive"; + + user_info->logon_parameters + = r->in.logon->password->identity_info.parameter_control; + user_info->client.account_name + = r->in.logon->password->identity_info.account_name.string; + user_info->client.domain_name + = r->in.logon->password->identity_info.domain_name.string; + user_info->workstation_name + = r->in.logon->password->identity_info.workstation.string; + user_info->flags |= USER_INFO_INTERACTIVE_LOGON; + user_info->password_state = AUTH_PASSWORD_HASH; + + user_info->password.hash.lanman = talloc(user_info, struct samr_Password); + NT_STATUS_HAVE_NO_MEMORY(user_info->password.hash.lanman); + *user_info->password.hash.lanman = r->in.logon->password->lmpassword; + + user_info->password.hash.nt = talloc(user_info, struct samr_Password); + NT_STATUS_HAVE_NO_MEMORY(user_info->password.hash.nt); + *user_info->password.hash.nt = r->in.logon->password->ntpassword; + + user_info->logon_id + = r->in.logon->password->identity_info.logon_id; + + break; + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + user_info->auth_description = "network"; + + nt_status = auth_context_set_challenge( + auth_context, + r->in.logon->network->challenge, + "netr_LogonSamLogonWithFlags"); + NT_STATUS_NOT_OK_RETURN(nt_status); + + user_info->logon_parameters + = r->in.logon->network->identity_info.parameter_control; + user_info->client.account_name + = r->in.logon->network->identity_info.account_name.string; + user_info->client.domain_name + = r->in.logon->network->identity_info.domain_name.string; + user_info->workstation_name + = r->in.logon->network->identity_info.workstation.string; + + user_info->password_state = AUTH_PASSWORD_RESPONSE; + user_info->password.response.lanman = data_blob_talloc(mem_ctx, r->in.logon->network->lm.data, r->in.logon->network->lm.length); + user_info->password.response.nt = data_blob_talloc(mem_ctx, r->in.logon->network->nt.data, r->in.logon->network->nt.length); + + user_info->logon_id + = r->in.logon->network->identity_info.logon_id; + + nt_status = NTLMv2_RESPONSE_verify_netlogon_creds( + user_info->client.account_name, + user_info->client.domain_name, + user_info->password.response.nt, + creds, workgroup); + NT_STATUS_NOT_OK_RETURN(nt_status); + + break; + + + case NetlogonGenericInformation: + { + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + /* OK */ + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + /* OK */ + } else { + /* Using DES to verify kerberos tickets makes no sense */ + return NT_STATUS_INVALID_PARAMETER; + } + + if (strcmp(r->in.logon->generic->package_name.string, "Kerberos") == 0) { + struct dcerpc_binding_handle *irpc_handle; + struct netr_GenericInfo2 *generic = talloc_zero(mem_ctx, struct netr_GenericInfo2); + NT_STATUS_HAVE_NO_MEMORY(generic); + + r->out.validation->generic = generic; + + user_info->logon_id + = r->in.logon->generic->identity_info.logon_id; + + irpc_handle = irpc_binding_handle_by_name(mem_ctx, + imsg_ctx, + "kdc_server", + &ndr_table_irpc); + if (irpc_handle == NULL) { + return NT_STATUS_NO_LOGON_SERVERS; + } + + state->kr.in.generic_request = + data_blob_const(r->in.logon->generic->data, + r->in.logon->generic->length); + + /* + * 60 seconds should be enough + */ + dcerpc_binding_handle_set_timeout(irpc_handle, 60); + subreq = dcerpc_kdc_check_generic_kerberos_r_send(state, + state->dce_call->event_ctx, + irpc_handle, &state->kr); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_netr_LogonSamLogon_base_krb5_done, + state); + return NT_STATUS_OK; + } + + /* Until we get an implemetnation of these other packages */ + return NT_STATUS_INVALID_PARAMETER; + } + default: + return NT_STATUS_INVALID_PARAMETER; + } + + subreq = auth_check_password_send(state, state->dce_call->event_ctx, + auth_context, user_info); + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_netr_LogonSamLogon_base_auth_done, + state); + return NT_STATUS_OK; +} + +static void dcesrv_netr_LogonSamLogon_base_auth_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_LogonSamLogon_base_state); + TALLOC_CTX *mem_ctx = state->mem_ctx; + struct netr_LogonSamLogonEx *r = &state->r; + struct auth_user_info_dc *user_info_dc = NULL; + struct netr_SamInfo2 *sam2 = NULL; + struct netr_SamInfo3 *sam3 = NULL; + struct netr_SamInfo6 *sam6 = NULL; + NTSTATUS nt_status; + + nt_status = auth_check_password_recv(subreq, mem_ctx, + &user_info_dc, + r->out.authoritative); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = nt_status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + switch (r->in.validation_level) { + case 2: + nt_status = auth_convert_user_info_dc_saminfo2(mem_ctx, + user_info_dc, + &sam2); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = nt_status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + r->out.validation->sam2 = sam2; + break; + + case 3: + nt_status = auth_convert_user_info_dc_saminfo3(mem_ctx, + user_info_dc, + &sam3); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = nt_status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + r->out.validation->sam3 = sam3; + break; + + case 6: + nt_status = auth_convert_user_info_dc_saminfo6(mem_ctx, + user_info_dc, + &sam6); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = nt_status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + r->out.validation->sam6 = sam6; + break; + + default: + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = NT_STATUS_INVALID_INFO_CLASS; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + } + + /* TODO: Describe and deal with these flags */ + *r->out.flags = 0; + + r->out.result = NT_STATUS_OK; + + dcesrv_netr_LogonSamLogon_base_reply(state); +} + +static void dcesrv_netr_LogonSamLogon_base_krb5_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_LogonSamLogon_base_state); + TALLOC_CTX *mem_ctx = state->mem_ctx; + struct netr_LogonSamLogonEx *r = &state->r; + struct netr_GenericInfo2 *generic = NULL; + NTSTATUS status; + + status = dcerpc_kdc_check_generic_kerberos_r_recv(subreq, mem_ctx); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + r->out.result = status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + generic = r->out.validation->generic; + generic->length = state->kr.out.generic_reply.length; + generic->data = state->kr.out.generic_reply.data; + + /* TODO: Describe and deal with these flags */ + *r->out.flags = 0; + + r->out.result = NT_STATUS_OK; + + dcesrv_netr_LogonSamLogon_base_reply(state); +} + +static void dcesrv_netr_LogonSamLogon_base_reply( + struct dcesrv_netr_LogonSamLogon_base_state *state) +{ + struct netr_LogonSamLogonEx *r = &state->r; + NTSTATUS status; + + if (NT_STATUS_IS_OK(r->out.result)) { + status = netlogon_creds_encrypt_samlogon_validation(state->creds, + r->in.validation_level, + r->out.validation); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("netlogon_creds_encrypt_samlogon_validation() " + "failed - %s\n", + nt_errstr(status)); + } + } + + if (state->_r.lslex != NULL) { + struct netr_LogonSamLogonEx *_r = state->_r.lslex; + _r->out.result = r->out.result; + } else if (state->_r.lslwf != NULL) { + struct netr_LogonSamLogonWithFlags *_r = state->_r.lslwf; + _r->out.result = r->out.result; + } else if (state->_r.lsl != NULL) { + struct netr_LogonSamLogon *_r = state->_r.lsl; + _r->out.result = r->out.result; + } + + status = dcesrv_reply(state->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("dcesrv_reply() failed - %s\n", + nt_errstr(status)); + } +} + +static NTSTATUS dcesrv_netr_LogonSamLogonEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonSamLogonEx *r) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state; + NTSTATUS nt_status; + + *r->out.authoritative = 1; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonSamLogon_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_name = r->in.server_name; + state->r.in.computer_name = r->in.computer_name; + state->r.in.logon_level = r->in.logon_level; + state->r.in.logon = r->in.logon; + state->r.in.validation_level = r->in.validation_level; + state->r.in.flags = r->in.flags; + state->r.out.validation = r->out.validation; + state->r.out.authoritative = r->out.authoritative; + state->r.out.flags = r->out.flags; + + state->_r.lslex = r; + + nt_status = dcesrv_netr_LogonSamLogon_check(dce_call, &state->r); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = schannel_get_creds_state(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + r->in.computer_name, &state->creds); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = dcesrv_netr_LogonSamLogon_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return nt_status; + } + + return nt_status; +} + +/* + netr_LogonSamLogonWithFlags + +*/ +static NTSTATUS dcesrv_netr_LogonSamLogonWithFlags(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonSamLogonWithFlags *r) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state; + NTSTATUS nt_status; + + *r->out.authoritative = 1; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonSamLogon_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_name = r->in.server_name; + state->r.in.computer_name = r->in.computer_name; + state->r.in.logon_level = r->in.logon_level; + state->r.in.logon = r->in.logon; + state->r.in.validation_level = r->in.validation_level; + state->r.in.flags = r->in.flags; + state->r.out.validation = r->out.validation; + state->r.out.authoritative = r->out.authoritative; + state->r.out.flags = r->out.flags; + + state->_r.lslwf = r; + + nt_status = dcesrv_netr_LogonSamLogon_check(dce_call, &state->r); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + r->out.return_authenticator = talloc_zero(mem_ctx, + struct netr_Authenticator); + if (r->out.return_authenticator == NULL) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &state->creds); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = dcesrv_netr_LogonSamLogon_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return nt_status; + } + + return nt_status; +} + +/* + netr_LogonSamLogon +*/ +static NTSTATUS dcesrv_netr_LogonSamLogon(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonSamLogon *r) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state; + NTSTATUS nt_status; + + *r->out.authoritative = 1; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonSamLogon_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_name = r->in.server_name; + state->r.in.computer_name = r->in.computer_name; + state->r.in.logon_level = r->in.logon_level; + state->r.in.logon = r->in.logon; + state->r.in.validation_level = r->in.validation_level; + state->r.in.flags = &state->_ignored_flags; + state->r.out.validation = r->out.validation; + state->r.out.authoritative = r->out.authoritative; + state->r.out.flags = &state->_ignored_flags; + + state->_r.lsl = r; + + nt_status = dcesrv_netr_LogonSamLogon_check(dce_call, &state->r); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + r->out.return_authenticator = talloc_zero(mem_ctx, + struct netr_Authenticator); + if (r->out.return_authenticator == NULL) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &state->creds); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = dcesrv_netr_LogonSamLogon_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return nt_status; + } + + return nt_status; +} + + +/* + netr_LogonSamLogoff +*/ +static NTSTATUS dcesrv_netr_LogonSamLogoff(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonSamLogoff *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + + +/* + netr_DatabaseDeltas +*/ +static NTSTATUS dcesrv_netr_DatabaseDeltas(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DatabaseDeltas *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_DatabaseSync2 +*/ +static NTSTATUS dcesrv_netr_DatabaseSync2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DatabaseSync2 *r) +{ + /* win2k3 native mode returns "NOT IMPLEMENTED" for this call */ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + netr_DatabaseSync +*/ +static NTSTATUS dcesrv_netr_DatabaseSync(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DatabaseSync *r) +{ + struct netr_DatabaseSync2 r2; + NTSTATUS status; + + ZERO_STRUCT(r2); + + r2.in.logon_server = r->in.logon_server; + r2.in.computername = r->in.computername; + r2.in.credential = r->in.credential; + r2.in.database_id = r->in.database_id; + r2.in.restart_state = SYNCSTATE_NORMAL_STATE; + r2.in.sync_context = r->in.sync_context; + r2.out.sync_context = r->out.sync_context; + r2.out.delta_enum_array = r->out.delta_enum_array; + r2.in.preferredmaximumlength = r->in.preferredmaximumlength; + + status = dcesrv_netr_DatabaseSync2(dce_call, mem_ctx, &r2); + + return status; +} + + +/* + netr_AccountDeltas +*/ +static NTSTATUS dcesrv_netr_AccountDeltas(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_AccountDeltas *r) +{ + /* w2k3 returns "NOT IMPLEMENTED" for this call */ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + netr_AccountSync +*/ +static NTSTATUS dcesrv_netr_AccountSync(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_AccountSync *r) +{ + /* w2k3 returns "NOT IMPLEMENTED" for this call */ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + netr_GetDcName +*/ +static WERROR dcesrv_netr_GetDcName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_GetDcName *r) +{ + const char * const attrs[] = { NULL }; + struct ldb_context *sam_ctx; + struct ldb_message **res; + struct ldb_dn *domain_dn; + int ret; + const char *dcname; + + /* + * [MS-NRPC] 3.5.5.3.4 NetrGetDCName says + * that the domainname needs to be a valid netbios domain + * name, if it is not NULL. + */ + if (r->in.domainname) { + const char *dot = strchr(r->in.domainname, '.'); + size_t len = strlen(r->in.domainname); + + if (dot || len > 15) { + return WERR_NERR_DCNOTFOUND; + } + + /* + * TODO: Should we also varify that only valid + * netbios name characters are used? + */ + } + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + domain_dn = samdb_domain_to_dn(sam_ctx, mem_ctx, + r->in.domainname); + if (domain_dn == NULL) { + return WERR_NO_SUCH_DOMAIN; + } + + ret = gendb_search_dn(sam_ctx, mem_ctx, + domain_dn, &res, attrs); + if (ret != 1) { + return WERR_NO_SUCH_DOMAIN; + } + + /* TODO: - return real IP address + * - check all r->in.* parameters (server_unc is ignored by w2k3!) + */ + dcname = talloc_asprintf(mem_ctx, "\\\\%s", + lpcfg_netbios_name(dce_call->conn->dce_ctx->lp_ctx)); + W_ERROR_HAVE_NO_MEMORY(dcname); + + *r->out.dcname = dcname; + return WERR_OK; +} + +struct dcesrv_netr_LogonControl_base_state { + struct dcesrv_call_state *dce_call; + + TALLOC_CTX *mem_ctx; + + struct netr_LogonControl2Ex r; + + struct { + struct netr_LogonControl *l; + struct netr_LogonControl2 *l2; + struct netr_LogonControl2Ex *l2ex; + } _r; +}; + +static void dcesrv_netr_LogonControl_base_done(struct tevent_req *subreq); + +static WERROR dcesrv_netr_LogonControl_base_call(struct dcesrv_netr_LogonControl_base_state *state) +{ + struct loadparm_context *lp_ctx = state->dce_call->conn->dce_ctx->lp_ctx; + struct auth_session_info *session_info = + dcesrv_call_session_info(state->dce_call); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(state->dce_call->conn); + enum security_user_level security_level; + struct dcerpc_binding_handle *irpc_handle; + struct tevent_req *subreq; + bool ok; + + /* TODO: check for WERR_INVALID_COMPUTERNAME ? */ + + if (state->_r.l != NULL) { + /* + * netr_LogonControl + */ + if (state->r.in.level == 0x00000002) { + return WERR_NOT_SUPPORTED; + } else if (state->r.in.level != 0x00000001) { + return WERR_INVALID_LEVEL; + } + + switch (state->r.in.function_code) { + case NETLOGON_CONTROL_QUERY: + case NETLOGON_CONTROL_REPLICATE: + case NETLOGON_CONTROL_SYNCHRONIZE: + case NETLOGON_CONTROL_PDC_REPLICATE: + case NETLOGON_CONTROL_BREAKPOINT: + case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: + case NETLOGON_CONTROL_TRUNCATE_LOG: + break; + default: + return WERR_NOT_SUPPORTED; + } + } + + if (state->r.in.level < 0x00000001) { + return WERR_INVALID_LEVEL; + } + + if (state->r.in.level > 0x00000004) { + return WERR_INVALID_LEVEL; + } + + if (state->r.in.function_code == NETLOGON_CONTROL_QUERY) { + struct netr_NETLOGON_INFO_1 *info1 = NULL; + struct netr_NETLOGON_INFO_3 *info3 = NULL; + + switch (state->r.in.level) { + case 0x00000001: + info1 = talloc_zero(state->mem_ctx, + struct netr_NETLOGON_INFO_1); + if (info1 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->r.out.query->info1 = info1; + return WERR_OK; + + case 0x00000003: + info3 = talloc_zero(state->mem_ctx, + struct netr_NETLOGON_INFO_3); + if (info3 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->r.out.query->info3 = info3; + return WERR_OK; + + default: + return WERR_INVALID_PARAMETER; + } + } + + /* + * Some validations are done before the access check + * and some after the access check + */ + security_level = security_session_user_level(session_info, NULL); + if (security_level < SECURITY_ADMINISTRATOR) { + return WERR_ACCESS_DENIED; + } + + if (state->_r.l2 != NULL) { + /* + * netr_LogonControl2 + */ + if (state->r.in.level == 0x00000004) { + return WERR_INVALID_LEVEL; + } + } + + switch (state->r.in.level) { + case 0x00000001: + break; + + case 0x00000002: + switch (state->r.in.function_code) { + case NETLOGON_CONTROL_REDISCOVER: + case NETLOGON_CONTROL_TC_QUERY: + case NETLOGON_CONTROL_TC_VERIFY: + break; + default: + return WERR_INVALID_PARAMETER; + } + + break; + + case 0x00000003: + break; + + case 0x00000004: + if (state->r.in.function_code != NETLOGON_CONTROL_FIND_USER) { + return WERR_INVALID_PARAMETER; + } + + break; + + default: + return WERR_INVALID_LEVEL; + } + + switch (state->r.in.function_code) { + case NETLOGON_CONTROL_REDISCOVER: + case NETLOGON_CONTROL_TC_QUERY: + case NETLOGON_CONTROL_TC_VERIFY: + if (state->r.in.level != 2) { + return WERR_INVALID_PARAMETER; + } + + if (state->r.in.data == NULL) { + return WERR_INVALID_PARAMETER; + } + + if (state->r.in.data->domain == NULL) { + return WERR_INVALID_PARAMETER; + } + + break; + + case NETLOGON_CONTROL_CHANGE_PASSWORD: + if (state->r.in.level != 1) { + return WERR_INVALID_PARAMETER; + } + + if (state->r.in.data == NULL) { + return WERR_INVALID_PARAMETER; + } + + if (state->r.in.data->domain == NULL) { + return WERR_INVALID_PARAMETER; + } + + ok = lpcfg_is_my_domain_or_realm(lp_ctx, + state->r.in.data->domain); + if (!ok) { + struct ldb_context *sam_ctx; + + sam_ctx = dcesrv_samdb_connect_as_system(state, + state->dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + /* + * Secrets for trusted domains can only be triggered on + * the PDC. + */ + ok = samdb_is_pdc(sam_ctx); + TALLOC_FREE(sam_ctx); + if (!ok) { + return WERR_INVALID_DOMAIN_ROLE; + } + } + + break; + default: + return WERR_NOT_SUPPORTED; + } + + irpc_handle = irpc_binding_handle_by_name(state, + imsg_ctx, + "winbind_server", + &ndr_table_winbind); + if (irpc_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for winbind_server task\n")); + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return WERR_SERVICE_NOT_FOUND; + } + + /* + * 60 seconds timeout should be enough + */ + dcerpc_binding_handle_set_timeout(irpc_handle, 60); + + subreq = dcerpc_winbind_LogonControl_send(state, + state->dce_call->event_ctx, + irpc_handle, + state->r.in.function_code, + state->r.in.level, + state->r.in.data, + state->r.out.query); + if (subreq == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_netr_LogonControl_base_done, + state); + + return WERR_OK; +} + +static void dcesrv_netr_LogonControl_base_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_LogonControl_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_LogonControl_base_state); + NTSTATUS status; + + status = dcerpc_winbind_LogonControl_recv(subreq, state->mem_ctx, + &state->r.out.result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + state->r.out.result = WERR_TIMEOUT; + } else if (!NT_STATUS_IS_OK(status)) { + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + DEBUG(0,(__location__ ": IRPC callback failed %s\n", + nt_errstr(status))); + } + + if (state->_r.l2ex != NULL) { + struct netr_LogonControl2Ex *r = state->_r.l2ex; + r->out.result = state->r.out.result; + } else if (state->_r.l2 != NULL) { + struct netr_LogonControl2 *r = state->_r.l2; + r->out.result = state->r.out.result; + } else if (state->_r.l != NULL) { + struct netr_LogonControl *r = state->_r.l; + r->out.result = state->r.out.result; + } + + status = dcesrv_reply(state->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", nt_errstr(status))); + } +} + +/* + netr_LogonControl +*/ +static WERROR dcesrv_netr_LogonControl(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonControl *r) +{ + struct dcesrv_netr_LogonControl_base_state *state; + WERROR werr; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonControl_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.logon_server = r->in.logon_server; + state->r.in.function_code = r->in.function_code; + state->r.in.level = r->in.level; + state->r.in.data = NULL; + state->r.out.query = r->out.query; + + state->_r.l = r; + + werr = dcesrv_netr_LogonControl_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return werr; + } + + return werr; +} + +/* + netr_LogonControl2 +*/ +static WERROR dcesrv_netr_LogonControl2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonControl2 *r) +{ + struct dcesrv_netr_LogonControl_base_state *state; + WERROR werr; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonControl_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.logon_server = r->in.logon_server; + state->r.in.function_code = r->in.function_code; + state->r.in.level = r->in.level; + state->r.in.data = r->in.data; + state->r.out.query = r->out.query; + + state->_r.l2 = r; + + werr = dcesrv_netr_LogonControl_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return werr; + } + + return werr; +} + +/* + netr_LogonControl2Ex +*/ +static WERROR dcesrv_netr_LogonControl2Ex(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonControl2Ex *r) +{ + struct dcesrv_netr_LogonControl_base_state *state; + WERROR werr; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonControl_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r = *r; + state->_r.l2ex = r; + + werr = dcesrv_netr_LogonControl_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return werr; + } + + return werr; +} + +static WERROR fill_trusted_domains_array(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct netr_DomainTrustList *trusts, + uint32_t trust_flags); + +/* + netr_GetAnyDCName +*/ +static WERROR dcesrv_netr_GetAnyDCName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_GetAnyDCName *r) +{ + struct netr_DomainTrustList *trusts; + struct ldb_context *sam_ctx; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + uint32_t i; + WERROR werr; + + *r->out.dcname = NULL; + + if ((r->in.domainname == NULL) || (r->in.domainname[0] == '\0')) { + /* if the domainname parameter wasn't set assume our domain */ + r->in.domainname = lpcfg_workgroup(lp_ctx); + } + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + if (strcasecmp(r->in.domainname, lpcfg_workgroup(lp_ctx)) == 0) { + /* well we asked for a DC of our own domain */ + if (samdb_is_pdc(sam_ctx)) { + /* we are the PDC of the specified domain */ + return WERR_NO_SUCH_DOMAIN; + } + + *r->out.dcname = talloc_asprintf(mem_ctx, "\\%s", + lpcfg_netbios_name(lp_ctx)); + W_ERROR_HAVE_NO_MEMORY(*r->out.dcname); + + return WERR_OK; + } + + /* Okay, now we have to consider the trusted domains */ + + trusts = talloc_zero(mem_ctx, struct netr_DomainTrustList); + W_ERROR_HAVE_NO_MEMORY(trusts); + + trusts->count = 0; + + werr = fill_trusted_domains_array(mem_ctx, sam_ctx, trusts, + NETR_TRUST_FLAG_INBOUND + | NETR_TRUST_FLAG_OUTBOUND); + W_ERROR_NOT_OK_RETURN(werr); + + for (i = 0; i < trusts->count; i++) { + if (strcasecmp(r->in.domainname, trusts->array[i].netbios_name) == 0) { + /* FIXME: Here we need to find a DC for the specified + * trusted domain. */ + + /* return WERR_OK; */ + return WERR_NO_SUCH_DOMAIN; + } + } + + return WERR_NO_SUCH_DOMAIN; +} + + +/* + netr_DatabaseRedo +*/ +static NTSTATUS dcesrv_netr_DatabaseRedo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DatabaseRedo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_NetrEnumerateTrustedDomains +*/ +static NTSTATUS dcesrv_netr_NetrEnumerateTrustedDomains(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NetrEnumerateTrustedDomains *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_LogonGetCapabilities +*/ +static NTSTATUS dcesrv_netr_LogonGetCapabilities(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonGetCapabilities *r) +{ + struct netlogon_creds_CredentialState *creds; + NTSTATUS status; + + switch (r->in.query_level) { + case 1: + break; + case 2: + /* + * Until we know the details behind KB5028166 + * just return DCERPC_NCA_S_FAULT_INVALID_TAG + * like an unpatched Windows Server. + */ + FALL_THROUGH; + default: + /* + * There would not be a way to marshall the + * the response. Which would mean our final + * ndr_push would fail an we would return + * an RPC-level fault with DCERPC_FAULT_BAD_STUB_DATA. + * + * But it's important to match a Windows server + * especially before KB5028166, see also our bug #15418 + * Otherwise Windows client would stop talking to us. + */ + DCESRV_FAULT(DCERPC_NCA_S_FAULT_INVALID_TAG); + } + + status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " Bad credentials - error\n")); + } + NT_STATUS_NOT_OK_RETURN(status); + + r->out.capabilities->server_capabilities = creds->negotiate_flags; + + return NT_STATUS_OK; +} + + +/* + netr_NETRLOGONSETSERVICEBITS +*/ +static WERROR dcesrv_netr_NETRLOGONSETSERVICEBITS(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NETRLOGONSETSERVICEBITS *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_LogonGetTrustRid +*/ +static WERROR dcesrv_netr_LogonGetTrustRid(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonGetTrustRid *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_NETRLOGONCOMPUTESERVERDIGEST +*/ +static WERROR dcesrv_netr_NETRLOGONCOMPUTESERVERDIGEST(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NETRLOGONCOMPUTESERVERDIGEST *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_NETRLOGONCOMPUTECLIENTDIGEST +*/ +static WERROR dcesrv_netr_NETRLOGONCOMPUTECLIENTDIGEST(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NETRLOGONCOMPUTECLIENTDIGEST *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + + +/* + netr_DsRGetSiteName +*/ +static WERROR dcesrv_netr_DsRGetSiteName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRGetSiteName *r) +{ + struct ldb_context *sam_ctx; + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + /* + * We assume to be a DC when we get called over NETLOGON. Hence we + * get our site name always by using "samdb_server_site_name()" + * and not "samdb_client_site_name()". + */ + *r->out.site = samdb_server_site_name(sam_ctx, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(*r->out.site); + + return WERR_OK; +} + + +/* + fill in a netr_OneDomainInfo from our own domain/forest +*/ +static NTSTATUS fill_our_one_domain_info(TALLOC_CTX *mem_ctx, + const struct lsa_TrustDomainInfoInfoEx *our_tdo, + struct GUID domain_guid, + struct netr_OneDomainInfo *info, + bool is_trust_list) +{ + ZERO_STRUCTP(info); + + if (is_trust_list) { + struct netr_trust_extension *te = NULL; + struct netr_trust_extension_info *tei = NULL; + + /* w2k8 only fills this on trusted domains */ + te = talloc_zero(mem_ctx, struct netr_trust_extension); + if (te == NULL) { + return NT_STATUS_NO_MEMORY; + } + tei = &te->info; + tei->flags |= NETR_TRUST_FLAG_PRIMARY; + + /* + * We're always within a native forest + */ + tei->flags |= NETR_TRUST_FLAG_IN_FOREST; + tei->flags |= NETR_TRUST_FLAG_NATIVE; + + /* For now we assume we're always the tree root */ + tei->flags |= NETR_TRUST_FLAG_TREEROOT; + tei->parent_index = 0; + + tei->trust_type = our_tdo->trust_type; + /* + * This needs to be 0 instead of our_tdo->trust_attributes + * It means LSA_TRUST_ATTRIBUTE_WITHIN_FOREST won't + * be set, while NETR_TRUST_FLAG_IN_FOREST is set above. + */ + tei->trust_attributes = 0; + + info->trust_extension.info = te; + } + + if (is_trust_list) { + info->dns_domainname.string = our_tdo->domain_name.string; + + /* MS-NRPC 3.5.4.3.9 - must be set to NULL for trust list */ + info->dns_forestname.string = NULL; + } else { + info->dns_domainname.string = talloc_asprintf(mem_ctx, "%s.", + our_tdo->domain_name.string); + if (info->dns_domainname.string == NULL) { + return NT_STATUS_NO_MEMORY; + } + + info->dns_forestname.string = info->dns_domainname.string; + } + + info->domainname.string = our_tdo->netbios_name.string; + info->domain_sid = our_tdo->sid; + info->domain_guid = domain_guid; + + return NT_STATUS_OK; +} + +/* + fill in a netr_OneDomainInfo from a trust tdo +*/ +static NTSTATUS fill_trust_one_domain_info(TALLOC_CTX *mem_ctx, + struct GUID domain_guid, + const struct lsa_TrustDomainInfoInfoEx *tdo, + struct netr_OneDomainInfo *info) +{ + struct netr_trust_extension *te = NULL; + struct netr_trust_extension_info *tei = NULL; + + ZERO_STRUCTP(info); + + /* w2k8 only fills this on trusted domains */ + te = talloc_zero(mem_ctx, struct netr_trust_extension); + if (te == NULL) { + return NT_STATUS_NO_MEMORY; + } + tei = &te->info; + + if (tdo->trust_direction & LSA_TRUST_DIRECTION_INBOUND) { + tei->flags |= NETR_TRUST_FLAG_INBOUND; + } + if (tdo->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND) { + tei->flags |= NETR_TRUST_FLAG_OUTBOUND; + } + if (tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + tei->flags |= NETR_TRUST_FLAG_IN_FOREST; + } + + /* + * TODO: once we support multiple domains within our forest, + * we need to fill this correct (or let the caller do it + * for all domains marked with NETR_TRUST_FLAG_IN_FOREST). + */ + tei->parent_index = 0; + + tei->trust_type = tdo->trust_type; + tei->trust_attributes = tdo->trust_attributes; + + info->trust_extension.info = te; + + info->domainname.string = tdo->netbios_name.string; + if (tdo->trust_type != LSA_TRUST_TYPE_DOWNLEVEL) { + info->dns_domainname.string = tdo->domain_name.string; + } else { + info->dns_domainname.string = NULL; + } + info->domain_sid = tdo->sid; + info->domain_guid = domain_guid; + + /* MS-NRPC 3.5.4.3.9 - must be set to NULL for trust list */ + info->dns_forestname.string = NULL; + + return NT_STATUS_OK; +} + +/* + netr_LogonGetDomainInfo + this is called as part of the ADS domain logon procedure. + + It has an important role in convaying details about the client, such + as Operating System, Version, Service Pack etc. +*/ +static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, struct netr_LogonGetDomainInfo *r) +{ + struct netlogon_creds_CredentialState *creds; + const char * const trusts_attrs[] = { + "securityIdentifier", + "flatName", + "trustPartner", + "trustAttributes", + "trustDirection", + "trustType", + NULL + }; + const char * const attrs2[] = { "sAMAccountName", "dNSHostName", + "msDS-SupportedEncryptionTypes", NULL }; + const char *sam_account_name, *old_dns_hostname; + struct ldb_context *sam_ctx; + const struct GUID *our_domain_guid = NULL; + struct lsa_TrustDomainInfoInfoEx *our_tdo = NULL; + struct ldb_message **res1, *new_msg; + struct ldb_result *trusts_res = NULL; + struct ldb_dn *workstation_dn; + struct netr_DomainInformation *domain_info; + struct netr_LsaPolicyInformation *lsa_policy_info; + struct auth_session_info *workstation_session_info = NULL; + uint32_t default_supported_enc_types = 0xFFFFFFFF; + bool update_dns_hostname = true; + int ret, i; + NTSTATUS status; + + status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(status)) { + char* local = NULL; + char* remote = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + remote = tsocket_address_string(dce_call->conn->remote_address, + frame); + local = tsocket_address_string(dce_call->conn->local_address, + frame); + DBG_ERR(("Bad credentials - " + "computer[%s] remote[%s] local[%s]\n"), + log_escape(frame, r->in.computer_name), + remote, + local); + talloc_free(frame); + } + NT_STATUS_NOT_OK_RETURN(status); + + /* We want to avoid connecting as system. */ + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + switch (r->in.level) { + case 1: /* Domain information */ + + if (r->in.query->workstation_info == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Prepares the workstation DN */ + workstation_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "<SID=%s>", + dom_sid_string(mem_ctx, creds->sid)); + NT_STATUS_HAVE_NO_MEMORY(workstation_dn); + + /* Get the workstation's session info from the database. */ + status = authsam_get_session_info_principal(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + sam_ctx, + NULL, /* principal */ + workstation_dn, + 0, /* session_info_flags */ + &workstation_session_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Reconnect to samdb as the workstation, now that we have its + * session info. We do this so the database update can be + * attributed to the workstation account in the audit logs -- + * otherwise it might be incorrectly attributed to + * SID_NT_ANONYMOUS. + */ + sam_ctx = dcesrv_samdb_connect_session_info(mem_ctx, + dce_call, + workstation_session_info, + workstation_session_info); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* Lookup for attributes in workstation object */ + ret = gendb_search_dn(sam_ctx, mem_ctx, workstation_dn, &res1, + attrs2); + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* Gets the sam account name which is checked against the DNS + * hostname parameter. */ + sam_account_name = ldb_msg_find_attr_as_string(res1[0], + "sAMAccountName", + NULL); + if (sam_account_name == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (r->in.query->workstation_info->dns_hostname == NULL) { + update_dns_hostname = false; + } + + /* Gets the old DNS hostname */ + old_dns_hostname = ldb_msg_find_attr_as_string(res1[0], + "dNSHostName", + NULL); + + /* + * Updates the DNS hostname when the client wishes that the + * server should handle this for him + * ("NETR_WS_FLAG_HANDLES_SPN_UPDATE" not set). + * See MS-NRPC section 3.5.4.3.9 + */ + if ((r->in.query->workstation_info->workstation_flags + & NETR_WS_FLAG_HANDLES_SPN_UPDATE) != 0) { + update_dns_hostname = false; + } + + /* Gets host information and put them into our directory */ + + new_msg = ldb_msg_new(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(new_msg); + + new_msg->dn = workstation_dn; + + /* Sets the OS name */ + + if (r->in.query->workstation_info->os_name.string == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + ret = ldb_msg_add_string(new_msg, "operatingSystem", + r->in.query->workstation_info->os_name.string); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + /* + * Sets information from "os_version". On an empty structure + * the values are cleared. + */ + if (r->in.query->workstation_info->os_version.os != NULL) { + struct netr_OsVersionInfoEx *os_version; + const char *os_version_str; + + os_version = &r->in.query->workstation_info->os_version.os->os; + + if (os_version->CSDVersion == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + os_version_str = talloc_asprintf(new_msg, "%u.%u (%u)", + os_version->MajorVersion, + os_version->MinorVersion, + os_version->BuildNumber); + NT_STATUS_HAVE_NO_MEMORY(os_version_str); + + if (strlen(os_version->CSDVersion) != 0) { + ret = ldb_msg_add_string(new_msg, + "operatingSystemServicePack", + os_version->CSDVersion); + } else { + ret = samdb_msg_add_delete(sam_ctx, mem_ctx, new_msg, + "operatingSystemServicePack"); + } + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(new_msg, + "operatingSystemVersion", + os_version_str); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } else { + ret = samdb_msg_add_delete(sam_ctx, mem_ctx, new_msg, + "operatingSystemServicePack"); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = samdb_msg_add_delete(sam_ctx, mem_ctx, new_msg, + "operatingSystemVersion"); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + /* + * If the boolean "update_dns_hostname" remained true, then we + * are fine to start the update. + */ + if (update_dns_hostname) { + ret = ldb_msg_add_string(new_msg, + "dNSHostname", + r->in.query->workstation_info->dns_hostname); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + /* This manual "servicePrincipalName" generation is + * still needed! Since the update in the samldb LDB + * module does only work if the entries already exist + * which isn't always the case. */ + ret = ldb_msg_add_string(new_msg, + "servicePrincipalName", + talloc_asprintf(new_msg, "HOST/%s", + r->in.computer_name)); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(new_msg, + "servicePrincipalName", + talloc_asprintf(new_msg, "HOST/%s", + r->in.query->workstation_info->dns_hostname)); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + if (dsdb_replace(sam_ctx, new_msg, DSDB_FLAG_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE) != LDB_SUCCESS) { + DEBUG(3,("Impossible to update samdb: %s\n", + ldb_errstring(sam_ctx))); + } + + talloc_free(new_msg); + + /* Writes back the domain information */ + + our_domain_guid = samdb_domain_guid(sam_ctx); + if (our_domain_guid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = dsdb_trust_local_tdo_info(mem_ctx, sam_ctx, &our_tdo); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dsdb_trust_search_tdos(sam_ctx, + NULL, /* exclude */ + trusts_attrs, + mem_ctx, + &trusts_res); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + domain_info = talloc(mem_ctx, struct netr_DomainInformation); + NT_STATUS_HAVE_NO_MEMORY(domain_info); + + ZERO_STRUCTP(domain_info); + + /* Informations about the local and trusted domains */ + + status = fill_our_one_domain_info(mem_ctx, + our_tdo, + *our_domain_guid, + &domain_info->primary_domain, + false); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + domain_info->trusted_domain_count = trusts_res->count + 1; + domain_info->trusted_domains = talloc_zero_array(mem_ctx, + struct netr_OneDomainInfo, + domain_info->trusted_domain_count); + NT_STATUS_HAVE_NO_MEMORY(domain_info->trusted_domains); + + for (i=0; i < trusts_res->count; i++) { + struct netr_OneDomainInfo *o = + &domain_info->trusted_domains[i]; + /* we can't know the guid of trusts outside our forest */ + struct GUID trust_domain_guid = GUID_zero(); + struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + + status = dsdb_trust_parse_tdo_info(mem_ctx, + trusts_res->msgs[i], + &tdo); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = fill_trust_one_domain_info(mem_ctx, + trust_domain_guid, + tdo, + o); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + status = fill_our_one_domain_info(mem_ctx, + our_tdo, + *our_domain_guid, + &domain_info->trusted_domains[i], + true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Sets the supported encryption types */ + domain_info->supported_enc_types = ldb_msg_find_attr_as_uint(res1[0], + "msDS-SupportedEncryptionTypes", + default_supported_enc_types); + + /* Other host domain information */ + + lsa_policy_info = talloc(mem_ctx, + struct netr_LsaPolicyInformation); + NT_STATUS_HAVE_NO_MEMORY(lsa_policy_info); + ZERO_STRUCTP(lsa_policy_info); + + domain_info->lsa_policy = *lsa_policy_info; + + /* The DNS hostname is only returned back when there is a chance + * for a change. */ + if ((r->in.query->workstation_info->workstation_flags + & NETR_WS_FLAG_HANDLES_SPN_UPDATE) != 0) { + domain_info->dns_hostname.string = old_dns_hostname; + } else { + domain_info->dns_hostname.string = NULL; + } + + domain_info->workstation_flags = + r->in.query->workstation_info->workstation_flags & ( + NETR_WS_FLAG_HANDLES_SPN_UPDATE | NETR_WS_FLAG_HANDLES_INBOUND_TRUSTS); + + r->out.info->domain_info = domain_info; + break; + case 2: /* LSA policy information - not used at the moment */ + lsa_policy_info = talloc(mem_ctx, + struct netr_LsaPolicyInformation); + NT_STATUS_HAVE_NO_MEMORY(lsa_policy_info); + ZERO_STRUCTP(lsa_policy_info); + + r->out.info->lsa_policy_info = lsa_policy_info; + break; + default: + return NT_STATUS_INVALID_LEVEL; + break; + } + + return NT_STATUS_OK; +} + + +/* + netr_ServerPasswordGet +*/ +static NTSTATUS dcesrv_netr_ServerPasswordGet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerPasswordGet *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +static bool sam_rodc_access_check(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct dom_sid *user_sid, + struct ldb_dn *obj_dn) +{ + const char *rodc_attrs[] = { "msDS-NeverRevealGroup", + "msDS-RevealOnDemandGroup", + "userAccountControl", + NULL }; + const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; + struct ldb_dn *rodc_dn; + int ret; + struct ldb_result *rodc_res = NULL, *obj_res = NULL; + WERROR werr; + + rodc_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "<SID=%s>", + dom_sid_string(mem_ctx, user_sid)); + if (!ldb_dn_validate(rodc_dn)) goto denied; + + /* + * do the two searches we need + * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID list + * out of the extended DNs + */ + ret = dsdb_search_dn(sam_ctx, mem_ctx, &rodc_res, rodc_dn, rodc_attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret != LDB_SUCCESS || rodc_res->count != 1) goto denied; + + ret = dsdb_search_dn(sam_ctx, mem_ctx, &obj_res, obj_dn, obj_attrs, 0); + if (ret != LDB_SUCCESS || obj_res->count != 1) goto denied; + + werr = samdb_confirm_rodc_allowed_to_repl_to(sam_ctx, + user_sid, + rodc_res->msgs[0], + obj_res->msgs[0]); + + if (W_ERROR_IS_OK(werr)) { + goto allowed; + } +denied: + return false; +allowed: + return true; + +} + +/* + netr_NetrLogonSendToSam +*/ +static NTSTATUS dcesrv_netr_NetrLogonSendToSam(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NetrLogonSendToSam *r) +{ + struct netlogon_creds_CredentialState *creds; + struct ldb_context *sam_ctx; + NTSTATUS nt_status; + DATA_BLOB decrypted_blob; + enum ndr_err_code ndr_err; + struct netr_SendToSamBase base_msg = { 0 }; + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + + NT_STATUS_NOT_OK_RETURN(nt_status); + + switch (creds->secure_channel_type) { + case SEC_CHAN_BDC: + case SEC_CHAN_RODC: + break; + case SEC_CHAN_WKSTA: + case SEC_CHAN_DNS_DOMAIN: + case SEC_CHAN_DOMAIN: + case SEC_CHAN_NULL: + return NT_STATUS_INVALID_PARAMETER; + default: + DEBUG(1, ("Client asked for an invalid secure channel type: %d\n", + creds->secure_channel_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* Buffer is meant to be 16-bit aligned */ + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + nt_status = netlogon_creds_aes_decrypt(creds, + r->in.opaque_buffer, + r->in.buffer_len); + } else { + nt_status = netlogon_creds_arcfour_crypt(creds, + r->in.opaque_buffer, + r->in.buffer_len); + } + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + decrypted_blob.data = r->in.opaque_buffer; + decrypted_blob.length = r->in.buffer_len; + + ndr_err = ndr_pull_struct_blob(&decrypted_blob, mem_ctx, &base_msg, + (ndr_pull_flags_fn_t)ndr_pull_netr_SendToSamBase); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + /* We only partially implement SendToSam */ + return NT_STATUS_NOT_IMPLEMENTED; + } + + /* Now 'send' to SAM */ + switch (base_msg.message_type) { + case SendToSamResetBadPasswordCount: + { + struct ldb_message *msg = ldb_msg_new(mem_ctx); + struct ldb_dn *dn = NULL; + int ret = 0; + + + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + ret = dsdb_find_dn_by_guid(sam_ctx, + mem_ctx, + &base_msg.message.reset_bad_password.guid, + 0, + &dn); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (creds->secure_channel_type == SEC_CHAN_RODC && + !sam_rodc_access_check(sam_ctx, mem_ctx, creds->sid, dn)) { + DEBUG(1, ("Client asked to reset bad password on " + "an arbitrary user: %s\n", + ldb_dn_get_linearized(dn))); + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + msg->dn = dn; + + ret = samdb_msg_add_int(sam_ctx, mem_ctx, msg, "badPwdCount", 0); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + ret = dsdb_replace(sam_ctx, msg, 0); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + ret = ldb_transaction_commit(sam_ctx); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + break; + } + default: + return NT_STATUS_NOT_IMPLEMENTED; + } + + return NT_STATUS_OK; +} + +struct dcesrv_netr_DsRGetDCName_base_state { + struct dcesrv_call_state *dce_call; + TALLOC_CTX *mem_ctx; + + struct netr_DsRGetDCNameEx2 r; + const char *client_site; + + struct { + struct netr_DsRGetDCName *dc; + struct netr_DsRGetDCNameEx *dcex; + struct netr_DsRGetDCNameEx2 *dcex2; + } _r; +}; + +static void dcesrv_netr_DsRGetDCName_base_done(struct tevent_req *subreq); + +/* Returns a nonzero value if multiple bits in 'val' are set. */ +static bool multiple_bits_set(uint32_t val) +{ + /* + * Subtracting one from an integer has the effect of flipping all the + * bits from the least significant bit up to and including the least + * significant '1' bit. For example, + * + * 0b101000 - 1 + * = 0b100111 + * ==== + * + * If 'val' is zero, all the bits will be flipped and thus the bitwise + * AND of 'val' with 'val - 1' will be zero. + * + * If the integer is nonzero, the least significant '1' bit will be + * ANDed with a '0' bit and so will be reset in the final result, but + * all other '1' bits will remain set. In other words, the effect of + * this expression is to mask off the least significant bit that is + * set. Therefore iff the result of 'val & (val - 1)' is non-zero, 'val' + * must contain multiple set bits. + */ + return val & (val - 1); +} + +static WERROR dcesrv_netr_DsRGetDCName_base_call(struct dcesrv_netr_DsRGetDCName_base_state *state) +{ + struct dcesrv_call_state *dce_call = state->dce_call; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + TALLOC_CTX *mem_ctx = state->mem_ctx; + struct netr_DsRGetDCNameEx2 *r = &state->r; + struct ldb_context *sam_ctx; + struct netr_DsRGetDCNameInfo *info; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + const struct tsocket_address *local_address; + char *local_addr = NULL; + const struct tsocket_address *remote_address; + char *remote_addr = NULL; + const char *server_site_name; + char *guid_str; + struct netlogon_samlogon_response response; + NTSTATUS status; + const char *dc_name = NULL; + const char *domain_name = NULL; + const char *pdc_ip; + bool different_domain = true; + uint32_t valid_flags; + int dc_level; + + ZERO_STRUCTP(r->out.info); + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + local_address = dcesrv_connection_get_local_address(dce_call->conn); + if (tsocket_address_is_inet(local_address, "ip")) { + local_addr = tsocket_address_inet_addr_string(local_address, state); + W_ERROR_HAVE_NO_MEMORY(local_addr); + } + + remote_address = dcesrv_connection_get_remote_address(dce_call->conn); + if (tsocket_address_is_inet(remote_address, "ip")) { + remote_addr = tsocket_address_inet_addr_string(remote_address, state); + W_ERROR_HAVE_NO_MEMORY(remote_addr); + } + + /* "server_unc" is ignored by w2k3 */ + + /* + * With the following flags: + * DS_FORCE_REDISCOVERY (Flag A) + * DS_DIRECTORY_SERVICE_REQUIRED (Flag B) + * DS_DIRECTORY_SERVICE_PREFERRED (Flag C) + * DS_GC_SERVER_REQUIRED (Flag D) + * DS_PDC_REQUIRED (Flag E) + * DS_BACKGROUND_ONLY (Flag F) + * DS_IP_REQUIRED (Flag G) + * DS_KDC_REQUIRED (Flag H) + * DS_TIMESERV_REQUIRED (Flag I) + * DS_WRITABLE_REQUIRED (Flag J) + * DS_GOOD_TIMESERV_PREFERRED (Flag K) + * DS_AVOID_SELF (Flag L) + * DS_ONLY_LDAP_NEEDED (Flag M) + * DS_IS_FLAT_NAME (Flag N) + * DS_IS_DNS_NAME (Flag O) + * DS_TRY_NEXTCLOSEST_SITE (Flag P) + * DS_DIRECTORY_SERVICE_6_REQUIRED (Flag Q) + * DS_WEB_SERVICE_REQUIRED (Flag T) + * DS_DIRECTORY_SERVICE_8_REQUIRED (Flag U) + * DS_DIRECTORY_SERVICE_9_REQUIRED (Flag V) + * DS_DIRECTORY_SERVICE_10_REQUIRED (Flag W) + * DS_RETURN_DNS_NAME (Flag R) + * DS_RETURN_FLAT_NAME (Flag S) + * + * MS-NRPC 3.5.4.3.1 says: + * ... + * On receiving this call, the server MUST perform the following Flags + * parameter validations: + * - Flags D, E, and H MUST NOT be combined with each other. + * - Flag N MUST NOT be combined with the O flag. + * - Flag R MUST NOT be combined with the S flag. + * - Flags B, Q, U, V, and W MUST NOT be combined with each other. + * - Flag K MUST NOT be combined with any of the flags: B, C, D, E, or H. + * - Flag P MUST NOT be set when the SiteName parameter is provided. + * The server MUST return ERROR_INVALID_FLAGS for any of the previously + * mentioned conflicting combinations. + * ... + */ + + dc_level = dsdb_dc_functional_level(sam_ctx); + valid_flags = DSGETDC_VALID_FLAGS; + if (dc_level >= DS_DOMAIN_FUNCTION_2012) { + valid_flags |= DS_DIRECTORY_SERVICE_8_REQUIRED; + } + if (dc_level >= DS_DOMAIN_FUNCTION_2012_R2) { + valid_flags |= DS_DIRECTORY_SERVICE_9_REQUIRED; + } + if (dc_level >= DS_DOMAIN_FUNCTION_2016) { + valid_flags |= DS_DIRECTORY_SERVICE_10_REQUIRED; + } + if (r->in.flags & ~valid_flags) { + /* + * TODO: add tests to prove this (maybe based on the + * msDS-Behavior-Version levels of dc, domain and/or forest + */ + return WERR_INVALID_FLAGS; + } + + /* Flags D, E, and H MUST NOT be combined with each other. */ +#define _DEH (DS_GC_SERVER_REQUIRED|DS_PDC_REQUIRED|DS_KDC_REQUIRED) + if (multiple_bits_set(r->in.flags & _DEH)) { + return WERR_INVALID_FLAGS; + } + + /* Flag N MUST NOT be combined with the O flag. */ + if (r->in.flags & DS_IS_FLAT_NAME && + r->in.flags & DS_IS_DNS_NAME) { + return WERR_INVALID_FLAGS; + } + + /* Flag R MUST NOT be combined with the S flag. */ + if (r->in.flags & DS_RETURN_DNS_NAME && + r->in.flags & DS_RETURN_FLAT_NAME) { + return WERR_INVALID_FLAGS; + } + + /* Flags B, Q, U, V, and W MUST NOT be combined with each other */ +#define _BQUVW ( \ + DS_DIRECTORY_SERVICE_REQUIRED | \ + DS_DIRECTORY_SERVICE_6_REQUIRED | \ + DS_DIRECTORY_SERVICE_8_REQUIRED | \ + DS_DIRECTORY_SERVICE_9_REQUIRED | \ + DS_DIRECTORY_SERVICE_10_REQUIRED | \ +0) + if (multiple_bits_set(r->in.flags & _BQUVW)) { + return WERR_INVALID_FLAGS; + } + + /* + * Flag K MUST NOT be combined with any of the flags: + * B, C, D, E, or H. + */ + if (r->in.flags & DS_GOOD_TIMESERV_PREFERRED && + r->in.flags & + (DS_DIRECTORY_SERVICE_REQUIRED | + DS_DIRECTORY_SERVICE_PREFERRED | + DS_GC_SERVER_REQUIRED | + DS_PDC_REQUIRED | + DS_KDC_REQUIRED)) { + return WERR_INVALID_FLAGS; + } + + /* Flag P MUST NOT be set when the SiteName parameter is provided. */ + if (r->in.flags & DS_TRY_NEXTCLOSEST_SITE && + r->in.site_name) { + return WERR_INVALID_FLAGS; + } + + /* + * If we send an all-zero GUID, we should ignore it as winbind actually + * checks it with a DNS query. Windows also appears to ignore it. + */ + if (r->in.domain_guid != NULL && GUID_all_zero(r->in.domain_guid)) { + r->in.domain_guid = NULL; + } + + /* Attempt winbind search only if we suspect the domain is incorrect */ + if (r->in.domain_name != NULL && strcmp("", r->in.domain_name) != 0) { + if (r->in.flags & DS_IS_FLAT_NAME) { + if (strcasecmp_m(r->in.domain_name, + lpcfg_sam_name(lp_ctx)) == 0) { + different_domain = false; + } + } else if (r->in.flags & DS_IS_DNS_NAME) { + if (strcasecmp_m(r->in.domain_name, + lpcfg_dnsdomain(lp_ctx)) == 0) { + different_domain = false; + } + } else { + if (strcasecmp_m(r->in.domain_name, + lpcfg_sam_name(lp_ctx)) == 0 || + strcasecmp_m(r->in.domain_name, + lpcfg_dnsdomain(lp_ctx)) == 0) { + different_domain = false; + } + } + } else { + /* + * We need to be able to handle empty domain names, where we + * revert to our domain by default. + */ + different_domain = false; + } + + /* Proof server site parameter "site_name" if it was specified */ + server_site_name = samdb_server_site_name(sam_ctx, state); + W_ERROR_HAVE_NO_MEMORY(server_site_name); + if (different_domain || (r->in.site_name != NULL && + (strcasecmp_m(r->in.site_name, + server_site_name) != 0))) { + + struct dcerpc_binding_handle *irpc_handle = NULL; + struct tevent_req *subreq = NULL; + + /* + * Retrieve the client site to override the winbind response. + * + * DO NOT use Windows fallback for client site. + * In the case of multiple domains, this is plainly wrong. + * + * Note: It's possible that the client may belong to multiple + * subnets across domains. It's not clear what this would mean, + * but here we only return what this domain knows. + */ + state->client_site = samdb_client_site_name(sam_ctx, + state, + remote_addr, + NULL, + false); + + irpc_handle = irpc_binding_handle_by_name(state, + imsg_ctx, + "winbind_server", + &ndr_table_winbind); + if (irpc_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for " + "winbind_server task\n")); + dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return WERR_SERVICE_NOT_FOUND; + } + + dcerpc_binding_handle_set_timeout(irpc_handle, 60); + + dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + + subreq = dcerpc_wbint_DsGetDcName_send(state, + dce_call->event_ctx, + irpc_handle, + r->in.domain_name, + r->in.domain_guid, + r->in.site_name, + r->in.flags, + r->out.info); + if (subreq == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + tevent_req_set_callback(subreq, + dcesrv_netr_DsRGetDCName_base_done, + state); + + return WERR_OK; + } + + guid_str = r->in.domain_guid != NULL ? + GUID_string(state, r->in.domain_guid) : NULL; + + status = fill_netlogon_samlogon_response(sam_ctx, mem_ctx, + r->in.domain_name, + r->in.domain_name, + NULL, guid_str, + r->in.client_account, + r->in.mask, remote_addr, + NETLOGON_NT_VERSION_5EX_WITH_IP, + lp_ctx, &response, true); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* + * According to MS-NRPC 2.2.1.2.1 we should set the "DS_DNS_FOREST_ROOT" + * (O) flag when the returned forest name is in DNS format. This is here + * always the case (see below). + */ + response.data.nt5_ex.server_type |= DS_DNS_FOREST_ROOT; + + if (r->in.flags & DS_RETURN_DNS_NAME) { + dc_name = response.data.nt5_ex.pdc_dns_name; + domain_name = response.data.nt5_ex.dns_domain; + /* + * According to MS-NRPC 2.2.1.2.1 we should set the + * "DS_DNS_CONTROLLER" (M) and "DS_DNS_DOMAIN" (N) flags when + * the returned information is in DNS form. + */ + response.data.nt5_ex.server_type |= + DS_DNS_CONTROLLER | DS_DNS_DOMAIN; + } else if (r->in.flags & DS_RETURN_FLAT_NAME) { + dc_name = response.data.nt5_ex.pdc_name; + domain_name = response.data.nt5_ex.domain_name; + } else { + + /* + * TODO: autodetect what we need to return + * based on the given arguments + */ + dc_name = response.data.nt5_ex.pdc_name; + domain_name = response.data.nt5_ex.domain_name; + } + + if (!dc_name || !dc_name[0]) { + return WERR_NO_SUCH_DOMAIN; + } + + if (!domain_name || !domain_name[0]) { + return WERR_NO_SUCH_DOMAIN; + } + + info = talloc(mem_ctx, struct netr_DsRGetDCNameInfo); + W_ERROR_HAVE_NO_MEMORY(info); + info->dc_unc = talloc_asprintf(mem_ctx, "%s%s", + dc_name[0] != '\\'? "\\\\":"", + talloc_strdup(mem_ctx, dc_name)); + W_ERROR_HAVE_NO_MEMORY(info->dc_unc); + + pdc_ip = local_addr; + if (pdc_ip == NULL) { + pdc_ip = "127.0.0.1"; + } + info->dc_address = talloc_asprintf(mem_ctx, "\\\\%s", pdc_ip); + W_ERROR_HAVE_NO_MEMORY(info->dc_address); + info->dc_address_type = DS_ADDRESS_TYPE_INET; + info->domain_guid = response.data.nt5_ex.domain_uuid; + info->domain_name = domain_name; + info->forest_name = response.data.nt5_ex.forest; + info->dc_flags = response.data.nt5_ex.server_type; + if (r->in.flags & DS_RETURN_DNS_NAME) { + /* As MS-NRPC.pdf in 2.2.1.2.1 the DS_DNS_CONTROLLER flag should be + * returned if we are returning info->dc_unc containing a FQDN. + * This attribute is called DomainControllerName in the specs, + * it seems that we decide to return FQDN or netbios depending on + * DS_RETURN_DNS_NAME. + */ + info->dc_flags |= DS_DNS_CONTROLLER; + } + info->dc_site_name = response.data.nt5_ex.server_site; + info->client_site_name = response.data.nt5_ex.client_site; + + *r->out.info = info; + + return WERR_OK; +} + +static void dcesrv_netr_DsRGetDCName_base_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_DsRGetDCName_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_DsRGetDCName_base_state); + struct dcesrv_call_state *dce_call = state->dce_call; + NTSTATUS result, status; + + status = dcerpc_wbint_DsGetDcName_recv(subreq, + state->mem_ctx, + &result); + TALLOC_FREE(subreq); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + state->r.out.result = WERR_TIMEOUT; + goto finished; + } + + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR(__location__ ": IRPC callback failed %s\n", + nt_errstr(status)); + state->r.out.result = WERR_GEN_FAILURE; + goto finished; + } + + if (!NT_STATUS_IS_OK(result)) { + DBG_NOTICE("DC location via winbind failed - %s\n", + nt_errstr(result)); + state->r.out.result = WERR_NO_SUCH_DOMAIN; + goto finished; + } + + if (state->r.out.info == NULL || state->r.out.info[0] == NULL) { + DBG_ERR("DC location via winbind returned no results\n"); + state->r.out.result = WERR_GEN_FAILURE; + goto finished; + } + + if (state->r.out.info[0]->dc_unc == NULL) { + DBG_ERR("DC location via winbind returned no DC unc\n"); + state->r.out.result = WERR_GEN_FAILURE; + goto finished; + } + + /* + * Either the supplied site name is NULL (possibly via + * TRY_NEXT_CLOSEST_SITE) or the resulting site name matches + * the input match name. + * + * TODO: Currently this means that requests with NETBIOS domain + * names can fail because they do not return the site name. + */ + if (state->r.in.site_name == NULL || + strcasecmp_m("", state->r.in.site_name) == 0 || + (state->r.out.info[0]->dc_site_name != NULL && + strcasecmp_m(state->r.out.info[0]->dc_site_name, + state->r.in.site_name) == 0)) { + + state->r.out.info[0]->client_site_name = + talloc_move(state->mem_ctx, &state->client_site); + + /* + * Make sure to return our DC UNC with // prefix. + * Winbind currently doesn't send the leading slashes + * for some reason. + */ + if (strlen(state->r.out.info[0]->dc_unc) > 2 && + strncmp("\\\\", state->r.out.info[0]->dc_unc, 2) != 0) { + const char *dc_unc = NULL; + + dc_unc = talloc_asprintf(state->mem_ctx, + "\\\\%s", + state->r.out.info[0]->dc_unc); + state->r.out.info[0]->dc_unc = dc_unc; + } + + state->r.out.result = WERR_OK; + } else { + state->r.out.info = NULL; + state->r.out.result = WERR_NO_SUCH_DOMAIN; + } + +finished: + if (state->_r.dcex2 != NULL) { + struct netr_DsRGetDCNameEx2 *r = state->_r.dcex2; + r->out.result = state->r.out.result; + } else if (state->_r.dcex != NULL) { + struct netr_DsRGetDCNameEx *r = state->_r.dcex; + r->out.result = state->r.out.result; + } else if (state->_r.dc != NULL) { + struct netr_DsRGetDCName *r = state->_r.dc; + r->out.result = state->r.out.result; + } + + TALLOC_FREE(state); + status = dcesrv_reply(dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", + nt_errstr(status))); + } +} + +/* + netr_DsRGetDCNameEx2 +*/ +static WERROR dcesrv_netr_DsRGetDCNameEx2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_DsRGetDCNameEx2 *r) +{ + struct dcesrv_netr_DsRGetDCName_base_state *state; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_DsRGetDCName_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r = *r; + state->_r.dcex2 = r; + + return dcesrv_netr_DsRGetDCName_base_call(state); +} + +/* + netr_DsRGetDCNameEx +*/ +static WERROR dcesrv_netr_DsRGetDCNameEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRGetDCNameEx *r) +{ + struct dcesrv_netr_DsRGetDCName_base_state *state; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_DsRGetDCName_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_unc = r->in.server_unc; + state->r.in.client_account = NULL; + state->r.in.mask = 0; + state->r.in.domain_guid = r->in.domain_guid; + state->r.in.domain_name = r->in.domain_name; + state->r.in.site_name = r->in.site_name; + state->r.in.flags = r->in.flags; + state->r.out.info = r->out.info; + + state->_r.dcex = r; + + return dcesrv_netr_DsRGetDCName_base_call(state); +} + +/* + * netr_DsRGetDCName + * + * This function is a predecessor to DsrGetDcNameEx2 according to [MS-NRPC]. + * Although it has a site-guid parameter, the documentation 3.5.4.3.3 DsrGetDcName + * insists that it be ignored. + */ +static WERROR dcesrv_netr_DsRGetDCName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRGetDCName *r) +{ + struct dcesrv_netr_DsRGetDCName_base_state *state; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_DsRGetDCName_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_unc = r->in.server_unc; + state->r.in.client_account = NULL; + state->r.in.mask = 0; + state->r.in.domain_name = r->in.domain_name; + state->r.in.domain_guid = r->in.domain_guid; + + state->r.in.site_name = NULL; /* this is correct, we should ignore site GUID */ + state->r.in.flags = r->in.flags; + state->r.out.info = r->out.info; + + state->_r.dc = r; + + return dcesrv_netr_DsRGetDCName_base_call(state); +} +/* + netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN +*/ +static WERROR dcesrv_netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_NetrEnumerateTrustedDomainsEx +*/ +static WERROR dcesrv_netr_NetrEnumerateTrustedDomainsEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NetrEnumerateTrustedDomainsEx *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_DsRAddressToSitenamesExW +*/ +static WERROR dcesrv_netr_DsRAddressToSitenamesExW(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRAddressToSitenamesExW *r) +{ + struct ldb_context *sam_ctx; + struct netr_DsRAddressToSitenamesExWCtr *ctr; + sa_family_t sin_family; + struct sockaddr_in *addr; +#ifdef HAVE_IPV6 + struct sockaddr_in6 *addr6; + char addr_str[INET6_ADDRSTRLEN]; +#else + char addr_str[INET_ADDRSTRLEN]; +#endif + char *subnet_name; + const char *res; + uint32_t i; + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + ctr = talloc(mem_ctx, struct netr_DsRAddressToSitenamesExWCtr); + W_ERROR_HAVE_NO_MEMORY(ctr); + + *r->out.ctr = ctr; + + ctr->count = r->in.count; + ctr->sitename = talloc_array(ctr, struct lsa_String, ctr->count); + W_ERROR_HAVE_NO_MEMORY(ctr->sitename); + ctr->subnetname = talloc_array(ctr, struct lsa_String, ctr->count); + W_ERROR_HAVE_NO_MEMORY(ctr->subnetname); + + for (i=0; i<ctr->count; i++) { + ctr->sitename[i].string = NULL; + ctr->subnetname[i].string = NULL; + + if (r->in.addresses[i].size < sizeof(sa_family_t)) { + continue; + } + /* The first two byte of the buffer are reserved for the + * "sin_family" but for now only the first one is used. */ + sin_family = r->in.addresses[i].buffer[0]; + + switch (sin_family) { + case AF_INET: + if (r->in.addresses[i].size < sizeof(struct sockaddr_in)) { + continue; + } + addr = (struct sockaddr_in *) r->in.addresses[i].buffer; + res = inet_ntop(AF_INET, &addr->sin_addr, + addr_str, sizeof(addr_str)); + break; +#ifdef HAVE_IPV6 + case AF_INET6: + if (r->in.addresses[i].size < sizeof(struct sockaddr_in6)) { + continue; + } + addr6 = (struct sockaddr_in6 *) r->in.addresses[i].buffer; + res = inet_ntop(AF_INET6, &addr6->sin6_addr, + addr_str, sizeof(addr_str)); + break; +#endif + default: + continue; + } + + if (res == NULL) { + continue; + } + + ctr->sitename[i].string = samdb_client_site_name(sam_ctx, + mem_ctx, + addr_str, + &subnet_name, + true); + W_ERROR_HAVE_NO_MEMORY(ctr->sitename[i].string); + ctr->subnetname[i].string = subnet_name; + } + + return WERR_OK; +} + + +/* + netr_DsRAddressToSitenamesW +*/ +static WERROR dcesrv_netr_DsRAddressToSitenamesW(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRAddressToSitenamesW *r) +{ + struct netr_DsRAddressToSitenamesExW r2; + struct netr_DsRAddressToSitenamesWCtr *ctr; + uint32_t i; + WERROR werr; + + ZERO_STRUCT(r2); + + r2.in.server_name = r->in.server_name; + r2.in.count = r->in.count; + r2.in.addresses = r->in.addresses; + + r2.out.ctr = talloc(mem_ctx, struct netr_DsRAddressToSitenamesExWCtr *); + W_ERROR_HAVE_NO_MEMORY(r2.out.ctr); + + ctr = talloc(mem_ctx, struct netr_DsRAddressToSitenamesWCtr); + W_ERROR_HAVE_NO_MEMORY(ctr); + + *r->out.ctr = ctr; + + ctr->count = r->in.count; + ctr->sitename = talloc_array(ctr, struct lsa_String, ctr->count); + W_ERROR_HAVE_NO_MEMORY(ctr->sitename); + + werr = dcesrv_netr_DsRAddressToSitenamesExW(dce_call, mem_ctx, &r2); + + for (i=0; i<ctr->count; i++) { + ctr->sitename[i].string = (*r2.out.ctr)->sitename[i].string; + } + + return werr; +} + + +/* + netr_DsrGetDcSiteCoverageW +*/ +static WERROR dcesrv_netr_DsrGetDcSiteCoverageW(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsrGetDcSiteCoverageW *r) +{ + struct ldb_context *sam_ctx; + struct DcSitesCtr *ctr; + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + ctr = talloc(mem_ctx, struct DcSitesCtr); + W_ERROR_HAVE_NO_MEMORY(ctr); + + *r->out.ctr = ctr; + + /* For now only return our default site */ + ctr->num_sites = 1; + ctr->sites = talloc_array(ctr, struct lsa_String, ctr->num_sites); + W_ERROR_HAVE_NO_MEMORY(ctr->sites); + ctr->sites[0].string = samdb_server_site_name(sam_ctx, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(ctr->sites[0].string); + + return WERR_OK; +} + + +static WERROR fill_trusted_domains_array(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct netr_DomainTrustList *trusts, + uint32_t trust_flags) +{ + struct ldb_dn *system_dn; + struct ldb_message **dom_res = NULL; + const char *trust_attrs[] = { "flatname", "trustPartner", + "securityIdentifier", "trustDirection", + "trustType", "trustAttributes", NULL }; + uint32_t n; + int i; + int ret; + + if (!(trust_flags & (NETR_TRUST_FLAG_INBOUND | + NETR_TRUST_FLAG_OUTBOUND))) { + return WERR_INVALID_FLAGS; + } + + system_dn = samdb_system_container_dn(sam_ctx, mem_ctx); + if (system_dn == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = gendb_search(sam_ctx, mem_ctx, system_dn, + &dom_res, trust_attrs, + "(objectclass=trustedDomain)"); + + for (i = 0; i < ret; i++) { + unsigned int trust_dir; + uint32_t flags = 0; + + trust_dir = ldb_msg_find_attr_as_uint(dom_res[i], + "trustDirection", 0); + + if (trust_dir & LSA_TRUST_DIRECTION_INBOUND) { + flags |= NETR_TRUST_FLAG_INBOUND; + } + if (trust_dir & LSA_TRUST_DIRECTION_OUTBOUND) { + flags |= NETR_TRUST_FLAG_OUTBOUND; + } + + if (!(flags & trust_flags)) { + /* this trust direction was not requested */ + continue; + } + + n = trusts->count; + trusts->array = talloc_realloc(trusts, trusts->array, + struct netr_DomainTrust, + n + 1); + W_ERROR_HAVE_NO_MEMORY(trusts->array); + + trusts->array[n].netbios_name = talloc_steal(trusts->array, ldb_msg_find_attr_as_string(dom_res[i], "flatname", NULL)); + if (!trusts->array[n].netbios_name) { + DEBUG(0, ("DB Error, TrustedDomain entry (%s) " + "without flatname\n", + ldb_dn_get_linearized(dom_res[i]->dn))); + } + + trusts->array[n].dns_name = talloc_steal(trusts->array, ldb_msg_find_attr_as_string(dom_res[i], "trustPartner", NULL)); + + trusts->array[n].trust_flags = flags; + if ((trust_flags & NETR_TRUST_FLAG_IN_FOREST) && + !(flags & NETR_TRUST_FLAG_TREEROOT)) { + /* TODO: find if we have parent in the list */ + trusts->array[n].parent_index = 0; + } + + trusts->array[n].trust_type = + ldb_msg_find_attr_as_uint(dom_res[i], + "trustType", 0); + trusts->array[n].trust_attributes = + ldb_msg_find_attr_as_uint(dom_res[i], + "trustAttributes", 0); + + if ((trusts->array[n].trust_type == LSA_TRUST_TYPE_MIT) || + (trusts->array[n].trust_type == LSA_TRUST_TYPE_DCE)) { + struct dom_sid zero_sid; + ZERO_STRUCT(zero_sid); + trusts->array[n].sid = + dom_sid_dup(trusts, &zero_sid); + } else { + trusts->array[n].sid = + samdb_result_dom_sid(trusts, dom_res[i], + "securityIdentifier"); + } + trusts->array[n].guid = GUID_zero(); + + trusts->count = n + 1; + } + + talloc_free(dom_res); + return WERR_OK; +} + +/* + netr_DsrEnumerateDomainTrusts +*/ +static WERROR dcesrv_netr_DsrEnumerateDomainTrusts(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_DsrEnumerateDomainTrusts *r) +{ + struct netr_DomainTrustList *trusts; + struct ldb_context *sam_ctx; + int ret; + struct ldb_message **dom_res; + const char * const dom_attrs[] = { "objectSid", "objectGUID", NULL }; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + const char *dnsdomain = lpcfg_dnsdomain(lp_ctx); + const char *p; + WERROR werr; + + if (r->in.trust_flags & 0xFFFFFE00) { + return WERR_INVALID_FLAGS; + } + + /* TODO: turn to hard check once we are sure this is 100% correct */ + if (!r->in.server_name) { + DEBUG(3, ("Invalid domain! Expected name in domain [%s]. " + "But received NULL!\n", dnsdomain)); + } else { + p = strchr(r->in.server_name, '.'); + if (!p) { + DEBUG(3, ("Invalid domain! Expected name in domain " + "[%s]. But received [%s]!\n", + dnsdomain, r->in.server_name)); + p = r->in.server_name; + } else { + p++; + } + if (strcasecmp(p, dnsdomain)) { + DEBUG(3, ("Invalid domain! Expected name in domain " + "[%s]. But received [%s]!\n", + dnsdomain, r->in.server_name)); + } + } + + trusts = talloc_zero(mem_ctx, struct netr_DomainTrustList); + W_ERROR_HAVE_NO_MEMORY(trusts); + + trusts->count = 0; + r->out.trusts = trusts; + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_GEN_FAILURE; + } + + if ((r->in.trust_flags & NETR_TRUST_FLAG_INBOUND) || + (r->in.trust_flags & NETR_TRUST_FLAG_OUTBOUND)) { + + werr = fill_trusted_domains_array(mem_ctx, sam_ctx, + trusts, r->in.trust_flags); + W_ERROR_NOT_OK_RETURN(werr); + } + + /* NOTE: we currently are always the root of the forest */ + if (r->in.trust_flags & NETR_TRUST_FLAG_IN_FOREST) { + uint32_t n = trusts->count; + + ret = gendb_search_dn(sam_ctx, mem_ctx, NULL, + &dom_res, dom_attrs); + if (ret != 1) { + return WERR_GEN_FAILURE; + } + + trusts->count = n + 1; + trusts->array = talloc_realloc(trusts, trusts->array, + struct netr_DomainTrust, + trusts->count); + W_ERROR_HAVE_NO_MEMORY(trusts->array); + + trusts->array[n].netbios_name = lpcfg_workgroup(lp_ctx); + trusts->array[n].dns_name = lpcfg_dnsdomain(lp_ctx); + trusts->array[n].trust_flags = + NETR_TRUST_FLAG_NATIVE | + NETR_TRUST_FLAG_TREEROOT | + NETR_TRUST_FLAG_IN_FOREST | + NETR_TRUST_FLAG_PRIMARY; + /* we are always the root domain for now */ + trusts->array[n].parent_index = 0; + trusts->array[n].trust_type = LSA_TRUST_TYPE_UPLEVEL; + trusts->array[n].trust_attributes = 0; + trusts->array[n].sid = samdb_result_dom_sid(mem_ctx, + dom_res[0], + "objectSid"); + trusts->array[n].guid = samdb_result_guid(dom_res[0], + "objectGUID"); + talloc_free(dom_res); + } + + return WERR_OK; +} + + +/* + netr_DsrDeregisterDNSHostRecords +*/ +static WERROR dcesrv_netr_DsrDeregisterDNSHostRecords(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsrDeregisterDNSHostRecords *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +static NTSTATUS dcesrv_netr_ServerGetTrustInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerGetTrustInfo *r); + +/* + netr_ServerTrustPasswordsGet +*/ +static NTSTATUS dcesrv_netr_ServerTrustPasswordsGet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerTrustPasswordsGet *r) +{ + struct netr_ServerGetTrustInfo r2 = {}; + struct netr_TrustInfo *_ti = NULL; + NTSTATUS status; + + r2.in.server_name = r->in.server_name; + r2.in.account_name = r->in.account_name; + r2.in.secure_channel_type = r->in.secure_channel_type; + r2.in.computer_name = r->in.computer_name; + r2.in.credential = r->in.credential; + + r2.out.return_authenticator = r->out.return_authenticator; + r2.out.new_owf_password = r->out.new_owf_password; + r2.out.old_owf_password = r->out.old_owf_password; + r2.out.trust_info = &_ti; + + status = dcesrv_netr_ServerGetTrustInfo(dce_call, mem_ctx, &r2); + + r->out.return_authenticator = r2.out.return_authenticator; + r->out.new_owf_password = r2.out.new_owf_password; + r->out.old_owf_password = r2.out.old_owf_password; + + return status; +} + +/* + netr_DsRGetForestTrustInformation +*/ +struct dcesrv_netr_DsRGetForestTrustInformation_state { + struct dcesrv_call_state *dce_call; + TALLOC_CTX *mem_ctx; + struct netr_DsRGetForestTrustInformation *r; +}; + +static void dcesrv_netr_DsRGetForestTrustInformation_done(struct tevent_req *subreq); + +static WERROR dcesrv_netr_DsRGetForestTrustInformation(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_DsRGetForestTrustInformation *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + enum security_user_level security_level; + struct ldb_context *sam_ctx = NULL; + struct dcesrv_netr_DsRGetForestTrustInformation_state *state = NULL; + struct dcerpc_binding_handle *irpc_handle = NULL; + struct tevent_req *subreq = NULL; + struct ldb_dn *domain_dn = NULL; + struct ldb_dn *forest_dn = NULL; + int cmp; + int forest_level; + + security_level = security_session_user_level(session_info, NULL); + if (security_level < SECURITY_USER) { + return WERR_ACCESS_DENIED; + } + + if (r->in.flags & 0xFFFFFFFE) { + return WERR_INVALID_FLAGS; + } + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_GEN_FAILURE; + } + + domain_dn = ldb_get_default_basedn(sam_ctx); + if (domain_dn == NULL) { + return WERR_GEN_FAILURE; + } + + forest_dn = ldb_get_root_basedn(sam_ctx); + if (forest_dn == NULL) { + return WERR_GEN_FAILURE; + } + + cmp = ldb_dn_compare(domain_dn, forest_dn); + if (cmp != 0) { + return WERR_NERR_ACFNOTLOADED; + } + + forest_level = dsdb_forest_functional_level(sam_ctx); + if (forest_level < DS_DOMAIN_FUNCTION_2003) { + return WERR_INVALID_FUNCTION; + } + + if (r->in.flags & DS_GFTI_UPDATE_TDO) { + if (!samdb_is_pdc(sam_ctx)) { + return WERR_NERR_NOTPRIMARY; + } + + if (r->in.trusted_domain_name == NULL) { + return WERR_INVALID_FLAGS; + } + } + + if (r->in.trusted_domain_name == NULL) { + NTSTATUS status; + + /* + * information about our own domain + */ + status = dsdb_trust_xref_forest_info(mem_ctx, sam_ctx, + r->out.forest_trust_info); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + return WERR_OK; + } + + /* + * Forward the request to winbindd + */ + + state = talloc_zero(mem_ctx, + struct dcesrv_netr_DsRGetForestTrustInformation_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + state->r = r; + + irpc_handle = irpc_binding_handle_by_name(state, + imsg_ctx, + "winbind_server", + &ndr_table_winbind); + if (irpc_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for winbind_server task\n")); + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return WERR_SERVICE_NOT_FOUND; + } + + /* + * 60 seconds timeout should be enough + */ + dcerpc_binding_handle_set_timeout(irpc_handle, 60); + + subreq = dcerpc_winbind_GetForestTrustInformation_send(state, + state->dce_call->event_ctx, + irpc_handle, + r->in.trusted_domain_name, + r->in.flags, + r->out.forest_trust_info); + if (subreq == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_netr_DsRGetForestTrustInformation_done, + state); + + return WERR_OK; +} + +static void dcesrv_netr_DsRGetForestTrustInformation_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_DsRGetForestTrustInformation_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_DsRGetForestTrustInformation_state); + NTSTATUS status; + + status = dcerpc_winbind_GetForestTrustInformation_recv(subreq, + state->mem_ctx, + &state->r->out.result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + state->r->out.result = WERR_TIMEOUT; + } else if (!NT_STATUS_IS_OK(status)) { + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + DEBUG(0,(__location__ ": IRPC callback failed %s\n", + nt_errstr(status))); + } + + status = dcesrv_reply(state->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", nt_errstr(status))); + } +} + +/* + netr_GetForestTrustInformation +*/ +static NTSTATUS dcesrv_netr_GetForestTrustInformation(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_GetForestTrustInformation *r) +{ + struct netlogon_creds_CredentialState *creds = NULL; + struct ldb_context *sam_ctx = NULL; + struct ldb_dn *domain_dn = NULL; + struct ldb_dn *forest_dn = NULL; + int cmp; + int forest_level; + NTSTATUS status; + + status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ((creds->secure_channel_type != SEC_CHAN_DNS_DOMAIN) && + (creds->secure_channel_type != SEC_CHAN_DOMAIN)) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + /* TODO: check r->in.server_name is our name */ + + domain_dn = ldb_get_default_basedn(sam_ctx); + if (domain_dn == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + forest_dn = ldb_get_root_basedn(sam_ctx); + if (forest_dn == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + cmp = ldb_dn_compare(domain_dn, forest_dn); + if (cmp != 0) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + forest_level = dsdb_forest_functional_level(sam_ctx); + if (forest_level < DS_DOMAIN_FUNCTION_2003) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + status = dsdb_trust_xref_forest_info(mem_ctx, sam_ctx, + r->out.forest_trust_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + + +/* + netr_ServerGetTrustInfo +*/ +static NTSTATUS dcesrv_netr_ServerGetTrustInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerGetTrustInfo *r) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + struct netlogon_creds_CredentialState *creds = NULL; + struct ldb_context *sam_ctx = NULL; + const char * const attrs[] = { + "unicodePwd", + "sAMAccountName", + "userAccountControl", + NULL + }; + struct ldb_message **res = NULL; + struct samr_Password *curNtHash = NULL, *prevNtHash = NULL; + NTSTATUS nt_status; + int ret; + const char *asid = NULL; + uint32_t uac = 0; + const char *aname = NULL; + struct ldb_message *tdo_msg = NULL; + const char * const tdo_attrs[] = { + "trustAuthIncoming", + "trustAttributes", + NULL + }; + struct netr_TrustInfo *trust_info = NULL; + + ZERO_STRUCTP(r->out.new_owf_password); + ZERO_STRUCTP(r->out.old_owf_password); + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + /* TODO: check r->in.server_name is our name */ + + if (strcasecmp_m(r->in.account_name, creds->account_name) != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.secure_channel_type != creds->secure_channel_type) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (strcasecmp_m(r->in.computer_name, creds->computer_name) != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + asid = ldap_encode_ndr_dom_sid(mem_ctx, creds->sid); + if (asid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = gendb_search(sam_ctx, mem_ctx, NULL, &res, attrs, + "(&(objectClass=user)(objectSid=%s))", + asid); + if (ret != 1) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + switch (creds->secure_channel_type) { + case SEC_CHAN_DNS_DOMAIN: + case SEC_CHAN_DOMAIN: + uac = ldb_msg_find_attr_as_uint(res[0], "userAccountControl", 0); + + if (uac & UF_ACCOUNTDISABLE) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + if (!(uac & UF_INTERDOMAIN_TRUST_ACCOUNT)) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + aname = ldb_msg_find_attr_as_string(res[0], "sAMAccountName", NULL); + if (aname == NULL) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + nt_status = dsdb_trust_search_tdo_by_type(sam_ctx, + SEC_CHAN_DOMAIN, aname, + tdo_attrs, mem_ctx, &tdo_msg); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return NT_STATUS_ACCOUNT_DISABLED; + } + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = dsdb_trust_get_incoming_passwords(tdo_msg, mem_ctx, + &curNtHash, + &prevNtHash); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + trust_info = talloc_zero(mem_ctx, struct netr_TrustInfo); + if (trust_info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + trust_info->count = 1; + trust_info->data = talloc_array(trust_info, uint32_t, + trust_info->count); + if (trust_info->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + trust_info->data[0] = ldb_msg_find_attr_as_uint(tdo_msg, + "trustAttributes", + 0); + break; + + default: + nt_status = samdb_result_passwords_no_lockout(mem_ctx, lp_ctx, + res[0], + &curNtHash); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + prevNtHash = talloc(mem_ctx, struct samr_Password); + if (prevNtHash == NULL) { + return NT_STATUS_NO_MEMORY; + } + + E_md4hash("", prevNtHash->hash); + break; + } + + if (curNtHash != NULL) { + *r->out.new_owf_password = *curNtHash; + nt_status = netlogon_creds_des_encrypt(creds, r->out.new_owf_password); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + if (prevNtHash != NULL) { + *r->out.old_owf_password = *prevNtHash; + nt_status = netlogon_creds_des_encrypt(creds, r->out.old_owf_password); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + + if (trust_info != NULL) { + *r->out.trust_info = trust_info; + } + + return NT_STATUS_OK; +} + +/* + netr_Unused47 +*/ +static NTSTATUS dcesrv_netr_Unused47(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_Unused47 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +struct netr_dnsupdate_RODC_state { + struct dcesrv_call_state *dce_call; + struct netr_DsrUpdateReadOnlyServerDnsRecords *r; + struct dnsupdate_RODC *r2; +}; + +/* + called when the forwarded RODC dns update request is finished + */ +static void netr_dnsupdate_RODC_callback(struct tevent_req *subreq) +{ + struct netr_dnsupdate_RODC_state *st = + tevent_req_callback_data(subreq, + struct netr_dnsupdate_RODC_state); + NTSTATUS status; + + status = dcerpc_dnsupdate_RODC_r_recv(subreq, st->dce_call); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": IRPC callback failed %s\n", nt_errstr(status))); + st->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + } + + st->r->out.dns_names = talloc_steal(st->dce_call, st->r2->out.dns_names); + + status = dcesrv_reply(st->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", nt_errstr(status))); + } +} + +/* + netr_DsrUpdateReadOnlyServerDnsRecords +*/ +static NTSTATUS dcesrv_netr_DsrUpdateReadOnlyServerDnsRecords(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_DsrUpdateReadOnlyServerDnsRecords *r) +{ + struct netlogon_creds_CredentialState *creds; + NTSTATUS nt_status; + struct dcerpc_binding_handle *binding_handle; + struct netr_dnsupdate_RODC_state *st; + struct tevent_req *subreq; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + NT_STATUS_NOT_OK_RETURN(nt_status); + + if (creds->secure_channel_type != SEC_CHAN_RODC) { + return NT_STATUS_ACCESS_DENIED; + } + + st = talloc_zero(mem_ctx, struct netr_dnsupdate_RODC_state); + NT_STATUS_HAVE_NO_MEMORY(st); + + st->dce_call = dce_call; + st->r = r; + st->r2 = talloc_zero(st, struct dnsupdate_RODC); + NT_STATUS_HAVE_NO_MEMORY(st->r2); + + st->r2->in.dom_sid = creds->sid; + st->r2->in.site_name = r->in.site_name; + st->r2->in.dns_ttl = r->in.dns_ttl; + st->r2->in.dns_names = r->in.dns_names; + st->r2->out.dns_names = r->out.dns_names; + + binding_handle = irpc_binding_handle_by_name(st, + imsg_ctx, + "dnsupdate", + &ndr_table_irpc); + if (binding_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for dnsupdate task\n")); + dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* forward the call */ + subreq = dcerpc_dnsupdate_RODC_r_send(st, dce_call->event_ctx, + binding_handle, st->r2); + NT_STATUS_HAVE_NO_MEMORY(subreq); + + dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + + /* setup the callback */ + tevent_req_set_callback(subreq, netr_dnsupdate_RODC_callback, st); + + return NT_STATUS_OK; +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_netlogon_s.c" |