X-Git-Url: http://git.samba.org/samba.git/?a=blobdiff_plain;f=source4%2Frpc_server%2Fnetlogon%2Fdcerpc_netlogon.c;h=e96cd08ce2db209fdb4a30114ded11e30e545f2a;hb=2099add0657126e4a5427ec2db0fe8025478b355;hp=3eaf0d4e1df2279df2169789b7d56e2cb54bc7f4;hpb=71572632bd33dcb5c03a701bbb72a707e5642237;p=garming%2Fsamba-autobuild%2F.git diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c index 3eaf0d4e1df..e96cd08ce2d 100644 --- a/source4/rpc_server/netlogon/dcerpc_netlogon.c +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -33,11 +33,35 @@ #include "lib/messaging/irpc.h" #include "librpc/gen_ndr/ndr_irpc_c.h" #include "../libcli/ldap/ldap_ndr.h" -#include "cldap_server/cldap_server.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 "lib/socket/netif.h" +#include "rpc_server/common/sid_helper.h" +#include "lib/util/util_str_escape.h" + +#define DCESRV_INTERFACE_NETLOGON_BIND(call, iface) \ + dcesrv_interface_netlogon_bind(call, iface) + +/* + * 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_call_state *dce_call, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_reject_connect(dce_call, iface); +} struct netlogon_server_pipe_state { struct netr_Credential client_challenge; @@ -49,11 +73,10 @@ static NTSTATUS dcesrv_netr_ServerReqChallenge(struct dcesrv_call_state *dce_cal { struct netlogon_server_pipe_state *pipe_state = talloc_get_type(dce_call->context->private_data, struct netlogon_server_pipe_state); + NTSTATUS ntstatus; ZERO_STRUCTP(r->out.return_credentials); - /* destroyed on pipe shutdown */ - if (pipe_state) { talloc_free(pipe_state); dce_call->context->private_data = NULL; @@ -71,63 +94,171 @@ static NTSTATUS dcesrv_netr_ServerReqChallenge(struct dcesrv_call_state *dce_cal dce_call->context->private_data = pipe_state; + 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)) { + return ntstatus; + } + return NT_STATUS_OK; } -static NTSTATUS dcesrv_netr_ServerAuthenticate3(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, - struct netr_ServerAuthenticate3 *r) +/* + * 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 = talloc_get_type(dce_call->context->private_data, struct netlogon_server_pipe_state); + bool challenge_valid = false; + struct netlogon_server_pipe_state challenge; struct netlogon_creds_CredentialState *creds; struct ldb_context *sam_ctx; - struct samr_Password *mach_pwd; + 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", NULL}; - - const char *trust_dom_attrs[] = {"flatname", NULL}; - const char *account_name; + "objectSid", "samAccountName", NULL}; + uint32_t server_flags = 0; uint32_t negotiate_flags = 0; + bool allow_nt4_crypto = lpcfg_allow_nt4_crypto(dce_call->conn->dce_ctx->lp_ctx); + bool reject_des_client = !allow_nt4_crypto; + bool reject_md5_client = lpcfg_reject_md5_clients(dce_call->conn->dce_ctx->lp_ctx); + int schannel = lpcfg_server_schannel(dce_call->conn->dce_ctx->lp_ctx); + bool reject_none_rpc = (schannel == true); ZERO_STRUCTP(r->out.return_credentials); *r->out.rid = 0; - negotiate_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_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_AUTHENTICATED_RPC_LSASS | - NETLOGON_NEG_AUTHENTICATED_RPC; - - if (*r->in.negotiate_flags & NETLOGON_NEG_STRONG_KEYS) { - negotiate_flags |= NETLOGON_NEG_STRONG_KEYS; - } - - if (*r->in.negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { - negotiate_flags |= NETLOGON_NEG_SUPPORTS_AES; + if (pipe_state != NULL) { + dce_call->context->private_data = 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; + + negotiate_flags = *r->in.negotiate_flags & server_flags; + + if (negotiate_flags & NETLOGON_NEG_AUTHENTICATED_RPC) { + reject_none_rpc = false; + } + + if (negotiate_flags & NETLOGON_NEG_STRONG_KEYS) { + reject_des_client = false; + } + + if (negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + reject_des_client = false; + reject_md5_client = false; + } + + if (reject_des_client || reject_md5_client) { + /* + * Here we match Windows 2012 and return no flags. + */ + *r->out.negotiate_flags = 0; + 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 @@ -136,6 +267,15 @@ static NTSTATUS dcesrv_netr_ServerAuthenticate3(struct dcesrv_call_state *dce_ca */ *r->out.negotiate_flags = negotiate_flags; + if (reject_none_rpc) { + /* schannel must be used, but client did not offer it. */ + DEBUG(0,("%s: schannel required but client failed " + "to offer it. Client was %s\n", + __func__, + log_escape(mem_ctx, r->in.account_name))); + return NT_STATUS_ACCESS_DENIED; + } + switch (r->in.secure_channel_type) { case SEC_CHAN_WKSTA: case SEC_CHAN_DNS_DOMAIN: @@ -143,82 +283,139 @@ static NTSTATUS dcesrv_netr_ServerAuthenticate3(struct dcesrv_call_state *dce_ca case SEC_CHAN_BDC: case SEC_CHAN_RODC: break; + case SEC_CHAN_NULL: + return NT_STATUS_INVALID_PARAMETER; default: DEBUG(1, ("Client asked for an invalid secure channel type: %d\n", r->in.secure_channel_type)); return NT_STATUS_INVALID_PARAMETER; } - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, - system_session(dce_call->conn->dce_ctx->lp_ctx), 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + dce_call->conn->dce_ctx->lp_ctx, + system_session(dce_call->conn->dce_ctx->lp_ctx), + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return NT_STATUS_INVALID_SYSTEM_SERVICE; } - if (r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { - char *encoded_account = ldb_binary_encode_string(mem_ctx, r->in.account_name); - const char *flatname; - if (!encoded_account) { + 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 NT_STATUS_NO_MEMORY; } - /* Kill the trailing dot */ - if (encoded_account[strlen(encoded_account)-1] == '.') { - encoded_account[strlen(encoded_account)-1] = '\0'; + len = strlen(encoded_name); + if (len < 2) { + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + + if (require_trailer && encoded_name[len - 1] != trailer) { + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; } + encoded_name[len - 1] = '\0'; - /* pull the user attributes */ - num_records = gendb_search(sam_ctx, mem_ctx, NULL, &msgs, - trust_dom_attrs, - "(&(trustPartner=%s)(objectclass=trustedDomain))", - encoded_account); + if (r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + dns = encoded_name; + } else { + netbios = encoded_name; + } - if (num_records == 0) { - DEBUG(3,("Couldn't find trust [%s] in samdb.\n", - encoded_account)); + 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 NT_STATUS_NO_TRUST_SAM_ACCOUNT; } + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } - if (num_records > 1) { - DEBUG(0,("Found %d records matching user [%s]\n", num_records, r->in.account_name)); - return NT_STATUS_INTERNAL_DB_CORRUPTION; + nt_status = dsdb_trust_get_incoming_passwords(tdo_msg, mem_ctx, + &curNtHash, + &prevNtHash); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED)) { + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; } - flatname = ldb_msg_find_attr_as_string(msgs[0], "flatname", NULL); - if (!flatname) { - /* No flatname for this trust - we can't proceed */ + flatname = ldb_msg_find_attr_as_string(tdo_msg, "flatName", NULL); + if (flatname == NULL) { return NT_STATUS_NO_TRUST_SAM_ACCOUNT; } - account_name = talloc_asprintf(mem_ctx, "%s$", flatname); - if (!account_name) { + *trust_account_for_search = talloc_asprintf(mem_ctx, "%s$", flatname); + if (*trust_account_for_search == NULL) { return NT_STATUS_NO_MEMORY; } - } else { - account_name = r->in.account_name; + *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, account_name)); + ldb_binary_encode_string(mem_ctx, + *trust_account_for_search)); if (num_records == 0) { DEBUG(3,("Couldn't find user [%s] in samdb.\n", - r->in.account_name)); + log_escape(mem_ctx, r->in.account_name))); return NT_STATUS_NO_TRUST_SAM_ACCOUNT; } if (num_records > 1) { - DEBUG(0,("Found %d records matching user [%s]\n", num_records, r->in.account_name)); + DEBUG(0,("Found %d records matching user [%s]\n", + num_records, + log_escape(mem_ctx, r->in.account_name))); return 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 NT_STATUS_INTERNAL_DB_CORRUPTION; + } + 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", r->in.account_name)); + DEBUG(1, ("Account [%s] is disabled\n", + log_escape(mem_ctx, r->in.account_name))); return NT_STATUS_NO_TRUST_SAM_ACCOUNT; } @@ -249,16 +446,24 @@ static NTSTATUS dcesrv_netr_ServerAuthenticate3(struct dcesrv_call_state *dce_ca return NT_STATUS_INTERNAL_ERROR; } - *r->out.rid = samdb_result_rid_from_sid(mem_ctx, msgs[0], - "objectSid", 0); + 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], NULL, &curNtHash); + if (!NT_STATUS_IS_OK(nt_status)) { + return NT_STATUS_ACCESS_DENIED; + } + } - mach_pwd = samdb_result_hash(mem_ctx, msgs[0], "unicodePwd"); - if (mach_pwd == NULL) { + if (curNtHash == NULL) { return NT_STATUS_ACCESS_DENIED; } - if (!pipe_state) { - DEBUG(1, ("No challenge requested by client, cannot authenticate\n")); + 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; } @@ -266,25 +471,102 @@ static NTSTATUS dcesrv_netr_ServerAuthenticate3(struct dcesrv_call_state *dce_ca r->in.account_name, r->in.computer_name, r->in.secure_channel_type, - &pipe_state->client_challenge, - &pipe_state->server_challenge, - mach_pwd, + &challenge.client_challenge, + &challenge.server_challenge, + curNtHash, r->in.credentials, r->out.return_credentials, negotiate_flags); - if (!creds) { - return NT_STATUS_ACCESS_DENIED; + 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; + } - 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 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( + dce_call->conn->msg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + NULL, + &ui, + status, + lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx), + trust_account_in_db, + NULL, + sid); + + return status; +} static NTSTATUS dcesrv_netr_ServerAuthenticate(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct netr_ServerAuthenticate *r) { @@ -342,7 +624,7 @@ static NTSTATUS dcesrv_netr_ServerAuthenticate2(struct dcesrv_call_state *dce_ca /* * If schannel is required for this call test that it actually is available. */ -static NTSTATUS schannel_check_required(struct dcerpc_auth *auth_info, +static NTSTATUS schannel_check_required(const struct dcesrv_auth *auth_info, const char *computer_name, bool integrity, bool privacy) { @@ -378,11 +660,11 @@ static NTSTATUS dcesrv_netr_creds_server_step_check(struct dcesrv_call_state *dc struct netlogon_creds_CredentialState **creds_out) { NTSTATUS nt_status; - struct dcerpc_auth *auth_info = dce_call->conn->auth_state.auth_info; - bool schannel_global_required = false; /* Should be lpcfg_schannel_server() == true */ + int schannel = lpcfg_server_schannel(dce_call->conn->dce_ctx->lp_ctx); + bool schannel_global_required = (schannel == true); if (schannel_global_required) { - nt_status = schannel_check_required(auth_info, + nt_status = schannel_check_required(&dce_call->conn->auth_state, computer_name, true, false); if (!NT_STATUS_IS_OK(nt_status)) { @@ -422,7 +704,12 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet(struct dcesrv_call_state *dce_call &creds); NT_STATUS_NOT_OK_RETURN(nt_status); - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, system_session(dce_call->conn->dce_ctx->lp_ctx), 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + dce_call->conn->dce_ctx->lp_ctx, + system_session(dce_call->conn->dce_ctx->lp_ctx), + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return NT_STATUS_INVALID_SYSTEM_SERVICE; } @@ -438,9 +725,9 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet(struct dcesrv_call_state *dce_call return NT_STATUS_WRONG_PASSWORD; } - nt_status = samdb_result_passwords(mem_ctx, - dce_call->conn->dce_ctx->lp_ctx, - res[0], NULL, &oldNtHash); + nt_status = samdb_result_passwords_no_lockout(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + res[0], NULL, &oldNtHash); if (!NT_STATUS_IS_OK(nt_status) || !oldNtHash) { return NT_STATUS_WRONG_PASSWORD; } @@ -448,6 +735,7 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet(struct dcesrv_call_state *dce_call /* 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 */ NULL, r->in.new_password, NULL, oldNtHash, /* Password change */ @@ -467,10 +755,11 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal const char * const attrs[] = { "dBCSPwd", "unicodePwd", NULL }; struct ldb_message **res; struct samr_Password *oldLmHash, *oldNtHash; + struct NL_PASSWORD_VERSION version = {}; + const uint32_t *new_version = NULL; NTSTATUS nt_status; DATA_BLOB new_password; int ret; - struct samr_CryptPassword password_buf; nt_status = dcesrv_netr_creds_server_step_check(dce_call, @@ -480,7 +769,12 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal &creds); NT_STATUS_NOT_OK_RETURN(nt_status); - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, system_session(dce_call->conn->dce_ctx->lp_ctx), 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + dce_call->conn->dce_ctx->lp_ctx, + system_session(dce_call->conn->dce_ctx->lp_ctx), + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return NT_STATUS_INVALID_SYSTEM_SERVICE; } @@ -494,6 +788,29 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal netlogon_creds_arcfour_crypt(creds, password_buf.data, 516); } + 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; @@ -508,9 +825,9 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal return NT_STATUS_WRONG_PASSWORD; } - nt_status = samdb_result_passwords(mem_ctx, - dce_call->conn->dce_ctx->lp_ctx, - res[0], &oldLmHash, &oldNtHash); + nt_status = samdb_result_passwords_no_lockout(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + res[0], &oldLmHash, &oldNtHash); if (!NT_STATUS_IS_OK(nt_status) || (!oldLmHash && !oldNtHash)) { return NT_STATUS_WRONG_PASSWORD; } @@ -518,6 +835,7 @@ static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_cal /* 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, NULL, oldLmHash, oldNtHash, /* Password change */ @@ -546,7 +864,8 @@ static WERROR dcesrv_netr_LogonUasLogoff(struct dcesrv_call_state *dce_call, TAL } -static NTSTATUS dcesrv_netr_LogonSamLogon_check(const struct netr_LogonSamLogonEx *r) +static NTSTATUS dcesrv_netr_LogonSamLogon_check(struct dcesrv_call_state *dce_call, + const struct netr_LogonSamLogonEx *r) { switch (r->in.logon_level) { case NetlogonInteractiveInformation: @@ -602,70 +921,146 @@ static NTSTATUS dcesrv_netr_LogonSamLogon_check(const struct netr_LogonSamLogonE return NT_STATUS_INVALID_PARAMETER; } + switch (r->in.validation_level) { + case NetlogonValidationSamInfo4: /* 6 */ + if (dce_call->conn->auth_state.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 completly, as this function must only run under schannel + We can't do the traditional 'wrapping' format completely, as this + function must only run under schannel */ -static NTSTATUS dcesrv_netr_LogonSamLogon_base(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, - struct netr_LogonSamLogonEx *r, struct netlogon_creds_CredentialState *creds) +static NTSTATUS dcesrv_netr_LogonSamLogon_base_call(struct dcesrv_netr_LogonSamLogon_base_state *state) { - struct auth4_context *auth_context; - struct auth_usersupplied_info *user_info; - struct auth_user_info_dc *user_info_dc; + struct dcesrv_call_state *dce_call = state->dce_call; + 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; - static const char zeros[16]; - struct netr_SamBaseInfo *sam; - struct netr_SamInfo2 *sam2; - struct netr_SamInfo3 *sam3; - struct netr_SamInfo6 *sam6; + struct tevent_req *subreq = NULL; *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"; + + netlogon_creds_decrypt_samlogon_logon(creds, + r->in.logon_level, + r->in.logon); + switch (r->in.logon_level) { case NetlogonInteractiveInformation: case NetlogonServiceInformation: case NetlogonInteractiveTransitiveInformation: case NetlogonServiceTransitiveInformation: - if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { - netlogon_creds_aes_decrypt(creds, - r->in.logon->password->lmpassword.hash, - sizeof(r->in.logon->password->lmpassword.hash)); - netlogon_creds_aes_decrypt(creds, - r->in.logon->password->ntpassword.hash, - sizeof(r->in.logon->password->ntpassword.hash)); - } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { - netlogon_creds_arcfour_crypt(creds, - r->in.logon->password->lmpassword.hash, - sizeof(r->in.logon->password->lmpassword.hash)); - netlogon_creds_arcfour_crypt(creds, - r->in.logon->password->ntpassword.hash, - sizeof(r->in.logon->password->ntpassword.hash)); - } else { - netlogon_creds_des_decrypt(creds, &r->in.logon->password->lmpassword); - netlogon_creds_des_decrypt(creds, &r->in.logon->password->ntpassword); - } + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: - /* TODO: we need to deny anonymous access here */ - nt_status = auth_context_create(mem_ctx, - dce_call->event_ctx, dce_call->msg_ctx, - dce_call->conn->dce_ctx->lp_ctx, - &auth_context); + nt_status = auth_context_create_for_netlogon(mem_ctx, + dce_call->event_ctx, dce_call->msg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + &auth_context); NT_STATUS_NOT_OK_RETURN(nt_status); - 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->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; + + 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; @@ -680,52 +1075,52 @@ static NTSTATUS dcesrv_netr_LogonSamLogon_base(struct dcesrv_call_state *dce_cal break; case NetlogonNetworkInformation: case NetlogonNetworkTransitiveInformation: + user_info->auth_description = "network"; - /* TODO: we need to deny anonymous access here */ - nt_status = auth_context_create(mem_ctx, - dce_call->event_ctx, dce_call->msg_ctx, - dce_call->conn->dce_ctx->lp_ctx, - &auth_context); + nt_status = auth_context_set_challenge( + auth_context, + r->in.logon->network->challenge, + "netr_LogonSamLogonWithFlags"); NT_STATUS_NOT_OK_RETURN(nt_status); - 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->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); + 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) { - netlogon_creds_aes_decrypt(creds, - r->in.logon->generic->data, r->in.logon->generic->length); + /* OK */ } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { - netlogon_creds_arcfour_crypt(creds, - r->in.logon->generic->data, r->in.logon->generic->length); + /* 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) { - NTSTATUS status; struct dcerpc_binding_handle *irpc_handle; - struct kdc_check_generic_kerberos check; struct netr_GenericInfo2 *generic = talloc_zero(mem_ctx, struct netr_GenericInfo2); NT_STATUS_HAVE_NO_MEMORY(generic); - *r->out.authoritative = 1; - - /* TODO: Describe and deal with these flags */ - *r->out.flags = 0; r->out.validation->generic = generic; @@ -737,18 +1132,24 @@ static NTSTATUS dcesrv_netr_LogonSamLogon_base(struct dcesrv_call_state *dce_cal return NT_STATUS_NO_LOGON_SERVERS; } - check.in.generic_request = + state->kr.in.generic_request = data_blob_const(r->in.logon->generic->data, r->in.logon->generic->length); - status = dcerpc_kdc_check_generic_kerberos_r(irpc_handle, - mem_ctx, - &check); - if (!NT_STATUS_IS_OK(status)) { - return status; + /* + * 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; } - generic->length = check.out.generic_reply.length; - generic->data = check.out.generic_reply.data; + 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; } @@ -759,129 +1160,205 @@ static NTSTATUS dcesrv_netr_LogonSamLogon_base(struct dcesrv_call_state *dce_cal return NT_STATUS_INVALID_PARAMETER; } - nt_status = auth_check_password(auth_context, mem_ctx, user_info, &user_info_dc); - /* TODO: set *r->out.authoritative = 0 on specific errors */ - NT_STATUS_NOT_OK_RETURN(nt_status); + 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_sambaseinfo(mem_ctx, user_info_dc, &sam); - NT_STATUS_NOT_OK_RETURN(nt_status); - - sam2 = talloc_zero(mem_ctx, struct netr_SamInfo2); - NT_STATUS_HAVE_NO_MEMORY(sam2); - sam2->base = *sam; + 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; + } - /* And put into the talloc tree */ - talloc_steal(sam2, sam); r->out.validation->sam2 = sam2; - - sam = &sam2->base; break; case 3: nt_status = auth_convert_user_info_dc_saminfo3(mem_ctx, - user_info_dc, - &sam3); - NT_STATUS_NOT_OK_RETURN(nt_status); + 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; - - sam = &sam3->base; break; case 6: - nt_status = auth_convert_user_info_dc_saminfo3(mem_ctx, - user_info_dc, - &sam3); - NT_STATUS_NOT_OK_RETURN(nt_status); - - sam6 = talloc_zero(mem_ctx, struct netr_SamInfo6); - NT_STATUS_HAVE_NO_MEMORY(sam6); - sam6->base = sam3->base; - sam = &sam6->base; - sam6->sidcount = sam3->sidcount; - sam6->sids = sam3->sids; - - sam6->dns_domainname.string = lpcfg_dnsdomain(dce_call->conn->dce_ctx->lp_ctx); - sam6->principle.string = talloc_asprintf(mem_ctx, "%s@%s", - sam->account_name.string, sam6->dns_domainname.string); - NT_STATUS_HAVE_NO_MEMORY(sam6->principle.string); - /* And put into the talloc tree */ - talloc_steal(sam6, sam3); + 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: - return NT_STATUS_INVALID_INFO_CLASS; - } - - /* Don't crypt an all-zero key, it would give away the NETLOGON pipe session key */ - /* It appears that level 6 is not individually encrypted */ - if ((r->in.validation_level != 6) && - memcmp(sam->key.key, zeros, sizeof(sam->key.key)) != 0) { - /* This key is sent unencrypted without the ARCFOUR or AES flag set */ - if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { - netlogon_creds_aes_encrypt(creds, - sam->key.key, - sizeof(sam->key.key)); - } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { - netlogon_creds_arcfour_crypt(creds, - sam->key.key, - sizeof(sam->key.key)); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = NT_STATUS_INVALID_INFO_CLASS; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; } } - /* Don't crypt an all-zero key, it would give away the NETLOGON pipe session key */ - /* It appears that level 6 is not individually encrypted */ - if ((r->in.validation_level != 6) && - memcmp(sam->LMSessKey.key, zeros, sizeof(sam->LMSessKey.key)) != 0) { - if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { - netlogon_creds_aes_encrypt(creds, - sam->LMSessKey.key, - sizeof(sam->LMSessKey.key)); - } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { - netlogon_creds_arcfour_crypt(creds, - sam->LMSessKey.key, - sizeof(sam->LMSessKey.key)); - } else { - netlogon_creds_des_encrypt_LMKey(creds, - &sam->LMSessKey); - } + /* 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; - return NT_STATUS_OK; + 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)) { + netlogon_creds_encrypt_samlogon_validation(state->creds, + r->in.validation_level, + r->out.validation); + } + + 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; - struct netlogon_creds_CredentialState *creds; *r->out.authoritative = 1; - nt_status = dcesrv_netr_LogonSamLogon_check(r); + 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, &creds); + r->in.computer_name, &state->creds); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } - if (!dce_call->conn->auth_state.auth_info || - dce_call->conn->auth_state.auth_info->auth_type != DCERPC_AUTH_TYPE_SCHANNEL) { + if (dce_call->conn->auth_state.auth_type != DCERPC_AUTH_TYPE_SCHANNEL) { return NT_STATUS_ACCESS_DENIED; } - return dcesrv_netr_LogonSamLogon_base(dce_call, mem_ctx, r, creds); + + nt_status = dcesrv_netr_LogonSamLogon_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return nt_status; + } + + return nt_status; } /* @@ -891,44 +1368,57 @@ static NTSTATUS dcesrv_netr_LogonSamLogonEx(struct dcesrv_call_state *dce_call, 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; - struct netlogon_creds_CredentialState *creds; - struct netr_LogonSamLogonEx r2; - struct netr_Authenticator *return_authenticator; + *r->out.authoritative = 1; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonSamLogon_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } - ZERO_STRUCT(r2); + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; - r2.in.server_name = r->in.server_name; - r2.in.computer_name = r->in.computer_name; - r2.in.logon_level = r->in.logon_level; - r2.in.logon = r->in.logon; - r2.in.validation_level = r->in.validation_level; - r2.in.flags = r->in.flags; - r2.out.validation = r->out.validation; - r2.out.authoritative = r->out.authoritative; - r2.out.flags = r->out.flags; + 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; - *r->out.authoritative = 1; + state->_r.lslwf = r; - nt_status = dcesrv_netr_LogonSamLogon_check(&r2); + nt_status = dcesrv_netr_LogonSamLogon_check(dce_call, &state->r); if (!NT_STATUS_IS_OK(nt_status)) { return nt_status; } - return_authenticator = talloc(mem_ctx, struct netr_Authenticator); - NT_STATUS_HAVE_NO_MEMORY(return_authenticator); + 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, return_authenticator, - &creds); - NT_STATUS_NOT_OK_RETURN(nt_status); + 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(dce_call, mem_ctx, &r2, creds); + nt_status = dcesrv_netr_LogonSamLogon_base_call(state); - r->out.return_authenticator = return_authenticator; + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return nt_status; + } return nt_status; } @@ -939,29 +1429,59 @@ static NTSTATUS dcesrv_netr_LogonSamLogonWithFlags(struct dcesrv_call_state *dce static NTSTATUS dcesrv_netr_LogonSamLogon(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct netr_LogonSamLogon *r) { - struct netr_LogonSamLogonWithFlags r2; - uint32_t flags = 0; - NTSTATUS status; + struct dcesrv_netr_LogonSamLogon_base_state *state; + NTSTATUS nt_status; - ZERO_STRUCT(r2); + *r->out.authoritative = 1; - r2.in.server_name = r->in.server_name; - r2.in.computer_name = r->in.computer_name; - r2.in.credential = r->in.credential; - r2.in.return_authenticator = r->in.return_authenticator; - r2.in.logon_level = r->in.logon_level; - r2.in.logon = r->in.logon; - r2.in.validation_level = r->in.validation_level; - r2.in.flags = &flags; - r2.out.validation = r->out.validation; - r2.out.authoritative = r->out.authoritative; - r2.out.flags = &flags; - - status = dcesrv_netr_LogonSamLogonWithFlags(dce_call, mem_ctx, &r2); + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonSamLogon_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } - r->out.return_authenticator = r2.out.return_authenticator; + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; - return status; + 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; } @@ -1069,7 +1589,7 @@ static WERROR dcesrv_netr_GetDcName(struct dcesrv_call_state *dce_call, TALLOC_C size_t len = strlen(r->in.domainname); if (dot || len > 15) { - return WERR_DCNOTFOUND; + return WERR_NERR_DCNOTFOUND; } /* @@ -1078,9 +1598,12 @@ static WERROR dcesrv_netr_GetDcName(struct dcesrv_call_state *dce_call, TALLOC_C */ } - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, - dce_call->conn->auth_state.session_info, 0); + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return WERR_DS_UNAVAILABLE; } @@ -1108,16 +1631,272 @@ static WERROR dcesrv_netr_GetDcName(struct dcesrv_call_state *dce_call, TALLOC_C return WERR_OK; } +struct dcesrv_netr_LogonControl_base_state { + struct dcesrv_call_state *dce_call; -/* - netr_LogonControl2Ex -*/ -static WERROR dcesrv_netr_LogonControl2Ex(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, - struct netr_LogonControl2Ex *r) + 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) { - return WERR_NOT_SUPPORTED; + struct dcesrv_connection *conn = state->dce_call->conn; + struct loadparm_context *lp_ctx = state->dce_call->conn->dce_ctx->lp_ctx; + struct auth_session_info *session_info = conn->auth_state.session_info; + 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 = samdb_connect( + state, + state->dce_call->event_ctx, + lp_ctx, + system_session(lp_ctx), + state->dce_call->conn->remote_address, + 0); + 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, + state->dce_call->msg_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 @@ -1125,47 +1904,93 @@ static WERROR dcesrv_netr_LogonControl2Ex(struct dcesrv_call_state *dce_call, TA static WERROR dcesrv_netr_LogonControl(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct netr_LogonControl *r) { - struct netr_LogonControl2Ex r2; + struct dcesrv_netr_LogonControl_base_state *state; WERROR werr; - if (r->in.level == 0x00000001) { - ZERO_STRUCT(r2); + 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; - r2.in.logon_server = r->in.logon_server; - r2.in.function_code = r->in.function_code; - r2.in.level = r->in.level; - r2.in.data = NULL; - r2.out.query = r->out.query; + 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; - werr = dcesrv_netr_LogonControl2Ex(dce_call, mem_ctx, &r2); - } else if (r->in.level == 0x00000002) { - werr = WERR_NOT_SUPPORTED; - } else { - werr = WERR_UNKNOWN_LEVEL; + 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 netr_LogonControl2Ex r2; + struct dcesrv_netr_LogonControl_base_state *state; WERROR werr; - ZERO_STRUCT(r2); + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonControl_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } - r2.in.logon_server = r->in.logon_server; - r2.in.function_code = r->in.function_code; - r2.in.level = r->in.level; - r2.in.data = r->in.data; - r2.out.query = r->out.query; + 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; + } - werr = dcesrv_netr_LogonControl2Ex(dce_call, mem_ctx, &r2); + 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; } @@ -1194,8 +2019,12 @@ static WERROR dcesrv_netr_GetAnyDCName(struct dcesrv_call_state *dce_call, TALLO r->in.domainname = lpcfg_workgroup(lp_ctx); } - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx, - dce_call->conn->auth_state.session_info, 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + lp_ctx, + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return WERR_DS_UNAVAILABLE; } @@ -1340,8 +2169,12 @@ static WERROR dcesrv_netr_DsRGetSiteName(struct dcesrv_call_state *dce_call, TAL struct ldb_context *sam_ctx; struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx, - dce_call->conn->auth_state.session_info, 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + lp_ctx, + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return WERR_DS_UNAVAILABLE; } @@ -1359,57 +2192,122 @@ static WERROR dcesrv_netr_DsRGetSiteName(struct dcesrv_call_state *dce_call, TAL /* - fill in a netr_OneDomainInfo from a ldb search result + fill in a netr_OneDomainInfo from our own domain/forest */ -static NTSTATUS fill_one_domain_info(TALLOC_CTX *mem_ctx, - struct loadparm_context *lp_ctx, - struct ldb_context *sam_ctx, - struct ldb_message *res, - struct netr_OneDomainInfo *info, - bool is_local, bool is_trust_list) +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 *tei = NULL; + /* w2k8 only fills this on trusted domains */ - info->trust_extension.info = talloc_zero(mem_ctx, struct netr_trust_extension); - info->trust_extension.length = 16; - info->trust_extension.info->flags = - NETR_TRUST_FLAG_TREEROOT | - NETR_TRUST_FLAG_IN_FOREST | - NETR_TRUST_FLAG_PRIMARY | - NETR_TRUST_FLAG_NATIVE; + tei = talloc_zero(mem_ctx, struct netr_trust_extension); + if (tei == NULL) { + return NT_STATUS_NO_MEMORY; + } + 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->parent_index = 0; /* should be index into array - of parent */ - info->trust_extension.info->trust_type = LSA_TRUST_TYPE_UPLEVEL; /* should be based on ldb search for trusts */ - info->trust_extension.info->trust_attributes = 0; /* TODO: base on ldb search? */ + info->trust_extension.info = tei; + info->trust_extension.length = 16; } 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_forestname.string = samdb_forest_name(sam_ctx, mem_ctx); - NT_STATUS_HAVE_NO_MEMORY(info->dns_forestname.string); - info->dns_forestname.string = talloc_asprintf(mem_ctx, "%s.", info->dns_forestname.string); - NT_STATUS_HAVE_NO_MEMORY(info->dns_forestname.string); + 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; } - if (is_local) { - info->domainname.string = lpcfg_workgroup(lp_ctx); - info->dns_domainname.string = lpcfg_dnsdomain(lp_ctx); - info->domain_guid = samdb_result_guid(res, "objectGUID"); - info->domain_sid = samdb_result_dom_sid(mem_ctx, res, "objectSid"); - } else { - info->domainname.string = ldb_msg_find_attr_as_string(res, "flatName", NULL); - info->dns_domainname.string = ldb_msg_find_attr_as_string(res, "trustPartner", NULL); - info->domain_guid = samdb_result_guid(res, "objectGUID"); - info->domain_sid = samdb_result_dom_sid(mem_ctx, res, "securityIdentifier"); + 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 *tei = NULL; + + ZERO_STRUCTP(info); + + /* w2k8 only fills this on trusted domains */ + tei = talloc_zero(mem_ctx, struct netr_trust_extension); + if (tei == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (tdo->trust_direction & LSA_TRUST_DIRECTION_INBOUND) { + tei->flags |= NETR_TRUST_FLAG_INBOUND; } - if (!is_trust_list) { - info->dns_domainname.string = talloc_asprintf(mem_ctx, "%s.", info->dns_domainname.string); + 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 = tei; + info->trust_extension.length = 16; + + 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; } @@ -1425,19 +2323,29 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal TALLOC_CTX *mem_ctx, struct netr_LogonGetDomainInfo *r) { struct netlogon_creds_CredentialState *creds; - const char * const attrs[] = { "objectSid", "objectGUID", "flatName", - "securityIdentifier", "trustPartner", NULL }; + 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, *prefix1, *prefix2; struct ldb_context *sam_ctx; - struct ldb_message **res1, **res2, **res3, *new_msg; + 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; uint32_t default_supported_enc_types = 0xFFFFFFFF; bool update_dns_hostname = true; - int ret, ret3, i; + int ret, i; NTSTATUS status; status = dcesrv_netr_creds_server_step_check(dce_call, @@ -1447,13 +2355,28 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal r->out.return_authenticator, &creds); if (!NT_STATUS_IS_OK(status)) { - DEBUG(0,(__location__ " Bad credentials - error\n")); + 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); - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx, - system_session(dce_call->conn->dce_ctx->lp_ctx), 0); + system_session(dce_call->conn->dce_ctx->lp_ctx), + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return NT_STATUS_INVALID_SYSTEM_SERVICE; } @@ -1634,21 +2557,23 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal /* Writes back the domain information */ - /* We need to do two searches. The first will pull our primary - domain and the second will pull any trusted domains. Our - primary domain is also a "trusted" domain, so we need to - put the primary domain into the lists of returned trusts as - well. */ - ret = gendb_search_dn(sam_ctx, mem_ctx, ldb_get_default_basedn(sam_ctx), - &res2, attrs); - if (ret != 1) { + our_domain_guid = samdb_domain_guid(sam_ctx); + if (our_domain_guid == NULL) { return NT_STATUS_INTERNAL_DB_CORRUPTION; } - ret3 = gendb_search(sam_ctx, mem_ctx, NULL, &res3, attrs, - "(objectClass=trustedDomain)"); - if (ret3 == -1) { - 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); @@ -1658,31 +2583,52 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal /* Informations about the local and trusted domains */ - status = fill_one_domain_info(mem_ctx, - dce_call->conn->dce_ctx->lp_ctx, - sam_ctx, res2[0], &domain_info->primary_domain, - true, false); - NT_STATUS_NOT_OK_RETURN(status); + 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 = ret3 + 1; - domain_info->trusted_domains = talloc_array(mem_ctx, + 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;iconn->dce_ctx->lp_ctx, - sam_ctx, res3[i], - &domain_info->trusted_domains[i], - false, true); - NT_STATUS_NOT_OK_RETURN(status); + 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_one_domain_info(mem_ctx, - dce_call->conn->dce_ctx->lp_ctx, sam_ctx, res2[0], - &domain_info->trusted_domains[i], true, true); - NT_STATUS_NOT_OK_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], @@ -1733,56 +2679,270 @@ static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_cal /* netr_ServerPasswordGet */ -static WERROR dcesrv_netr_ServerPasswordGet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, +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); } - -/* - netr_NETRLOGONSENDTOSAM -*/ -static WERROR dcesrv_netr_NETRLOGONSENDTOSAM(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, - struct netr_NETRLOGONSENDTOSAM *r) +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) { - DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); -} + const char *rodc_attrs[] = { "msDS-KrbTgtLink", "msDS-NeverRevealGroup", "msDS-RevealOnDemandGroup", "objectGUID", 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; + const struct dom_sid *additional_sids[] = { NULL, NULL }; + WERROR werr; + struct dom_sid *object_sid; + const struct dom_sid **never_reveal_sids, **reveal_sids, **token_sids; + rodc_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "", + dom_sid_string(mem_ctx, user_sid)); + if (!ldb_dn_validate(rodc_dn)) goto denied; -/* - netr_DsRGetDCNameEx2 -*/ -static WERROR dcesrv_netr_DsRGetDCNameEx2(struct dcesrv_call_state *dce_call, - TALLOC_CTX *mem_ctx, - struct netr_DsRGetDCNameEx2 *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 *remote_address; - char *addr = NULL; - const char *server_site_name; - char *guid_str; - struct netlogon_samlogon_response response; - NTSTATUS status; + /* do the two searches we need */ + 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; + + object_sid = samdb_result_dom_sid(mem_ctx, obj_res->msgs[0], "objectSid"); + + additional_sids[0] = object_sid; + + werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0], + mem_ctx, "msDS-NeverRevealGroup", &never_reveal_sids); + if (!W_ERROR_IS_OK(werr)) { + goto denied; + } + + werr = samdb_result_sid_array_dn(sam_ctx, rodc_res->msgs[0], + mem_ctx, "msDS-RevealOnDemandGroup", &reveal_sids); + if (!W_ERROR_IS_OK(werr)) { + goto denied; + } + + /* + * The SID list needs to include itself as well as the tokenGroups. + * + * TODO determine if sIDHistory is required for this check + */ + werr = samdb_result_sid_array_ndr(sam_ctx, obj_res->msgs[0], + mem_ctx, "tokenGroups", &token_sids, + additional_sids, 1); + if (!W_ERROR_IS_OK(werr) || token_sids==NULL) { + goto denied; + } + + if (never_reveal_sids && + sid_list_match(token_sids, never_reveal_sids)) { + goto denied; + } + + if (reveal_sids && + sid_list_match(token_sids, reveal_sids)) { + 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 = samdb_connect(mem_ctx, + dce_call->event_ctx, + dce_call->conn->dce_ctx->lp_ctx, + system_session(dce_call->conn->dce_ctx->lp_ctx), + dce_call->conn->remote_address, + 0); + 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) { + netlogon_creds_aes_decrypt(creds, r->in.opaque_buffer, r->in.buffer_len); + } else { + netlogon_creds_arcfour_crypt(creds, r->in.opaque_buffer, r->in.buffer_len); + } + + 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); + +static WERROR dcesrv_netr_DsRGetDCName_base_call(struct dcesrv_netr_DsRGetDCName_base_state *state) +{ + struct dcesrv_call_state *dce_call = state->dce_call; + 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; - struct interface *ifaces; const char *pdc_ip; + bool different_domain = true; ZERO_STRUCTP(r->out.info); - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx, - dce_call->conn->auth_state.session_info, 0); + sam_ctx = samdb_connect(state, + dce_call->event_ctx, + lp_ctx, + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); 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")) { - addr = tsocket_address_inet_addr_string(remote_address, mem_ctx); - W_ERROR_HAVE_NO_MEMORY(addr); + remote_addr = tsocket_address_inet_addr_string(remote_address, state); + W_ERROR_HAVE_NO_MEMORY(remote_addr); } /* "server_unc" is ignored by w2k3 */ @@ -1824,23 +2984,111 @@ static WERROR dcesrv_netr_DsRGetDCNameEx2(struct dcesrv_call_state *dce_call, 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, mem_ctx); + server_site_name = samdb_server_site_name(sam_ctx, state); W_ERROR_HAVE_NO_MEMORY(server_site_name); - if ((r->in.site_name != NULL) && (strcasecmp(r->in.site_name, - server_site_name) != 0)) { - return WERR_NO_SUCH_DOMAIN; + 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, + dce_call->msg_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(mem_ctx, 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, addr, + r->in.mask, remote_addr, NETLOGON_NT_VERSION_5EX_WITH_IP, lp_ctx, &response, true); if (!NT_STATUS_IS_OK(status)) { @@ -1887,11 +3135,12 @@ static WERROR dcesrv_netr_DsRGetDCNameEx2(struct dcesrv_call_state *dce_call, info = talloc(mem_ctx, struct netr_DsRGetDCNameInfo); W_ERROR_HAVE_NO_MEMORY(info); - info->dc_unc = talloc_asprintf(mem_ctx, "\\\\%s", dc_name); + 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); - load_interface_list(mem_ctx, lp_ctx, &ifaces); - pdc_ip = iface_list_best_ip(ifaces, addr); + pdc_ip = local_addr; if (pdc_ip == NULL) { pdc_ip = "127.0.0.1"; } @@ -1902,6 +3151,15 @@ static WERROR dcesrv_netr_DsRGetDCNameEx2(struct dcesrv_call_state *dce_call, 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; @@ -1910,55 +3168,194 @@ static WERROR dcesrv_netr_DsRGetDCNameEx2(struct dcesrv_call_state *dce_call, 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 netr_DsRGetDCNameEx2 r2; - WERROR werr; + struct dcesrv_netr_DsRGetDCName_base_state *state; - ZERO_STRUCT(r2); + state = talloc_zero(mem_ctx, struct dcesrv_netr_DsRGetDCName_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } - r2.in.server_unc = r->in.server_unc; - r2.in.client_account = NULL; - r2.in.mask = 0; - r2.in.domain_guid = r->in.domain_guid; - r2.in.domain_name = r->in.domain_name; - r2.in.site_name = r->in.site_name; - r2.in.flags = r->in.flags; - r2.out.info = r->out.info; + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; - werr = dcesrv_netr_DsRGetDCNameEx2(dce_call, mem_ctx, &r2); + 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; - return werr; + state->_r.dcex = r; + + return dcesrv_netr_DsRGetDCName_base_call(state); } /* - netr_DsRGetDCName -*/ + * 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 netr_DsRGetDCName *r) { - struct netr_DsRGetDCNameEx2 r2; - WERROR werr; + struct dcesrv_netr_DsRGetDCName_base_state *state; - ZERO_STRUCT(r2); + state = talloc_zero(mem_ctx, struct dcesrv_netr_DsRGetDCName_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } - r2.in.server_unc = r->in.server_unc; - r2.in.client_account = NULL; - r2.in.mask = 0; - r2.in.domain_name = r->in.domain_name; - r2.in.domain_guid = r->in.domain_guid; + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; - r2.in.site_name = NULL; /* this is correct, we should ignore site GUID */ - r2.in.flags = r->in.flags; - r2.out.info = r->out.info; + 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; - werr = dcesrv_netr_DsRGetDCNameEx2(dce_call, mem_ctx, &r2); + 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; - return werr; + state->_r.dc = r; + + return dcesrv_netr_DsRGetDCName_base_call(state); } /* netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN @@ -2001,8 +3398,12 @@ static WERROR dcesrv_netr_DsRAddressToSitenamesExW(struct dcesrv_call_state *dce const char *res; uint32_t i; - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx, - dce_call->conn->auth_state.session_info, 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + lp_ctx, + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return WERR_DS_UNAVAILABLE; } @@ -2059,7 +3460,8 @@ static WERROR dcesrv_netr_DsRAddressToSitenamesExW(struct dcesrv_call_state *dce ctr->sitename[i].string = samdb_client_site_name(sam_ctx, mem_ctx, addr_str, - &subnet_name); + &subnet_name, + true); W_ERROR_HAVE_NO_MEMORY(ctr->sitename[i].string); ctr->subnetname[i].string = subnet_name; } @@ -2117,8 +3519,12 @@ static WERROR dcesrv_netr_DsrGetDcSiteCoverageW(struct dcesrv_call_state *dce_ca struct DcSitesCtr *ctr; struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx, - dce_call->conn->auth_state.session_info, 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + lp_ctx, + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return WERR_DS_UNAVAILABLE; } @@ -2162,7 +3568,7 @@ static WERROR fill_trusted_domains_array(TALLOC_CTX *mem_ctx, ldb_get_default_basedn(sam_ctx), "(&(objectClass=container)(cn=System))"); if (!system_dn) { - return WERR_GENERAL_FAILURE; + return WERR_GEN_FAILURE; } ret = gendb_search(sam_ctx, mem_ctx, system_dn, @@ -2217,8 +3623,8 @@ static WERROR fill_trusted_domains_array(TALLOC_CTX *mem_ctx, ldb_msg_find_attr_as_uint(dom_res[i], "trustAttributes", 0); - if ((trusts->array[n].trust_type == NETR_TRUST_TYPE_MIT) || - (trusts->array[n].trust_type == NETR_TRUST_TYPE_DCE)) { + 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 = @@ -2285,10 +3691,14 @@ static WERROR dcesrv_netr_DsrEnumerateDomainTrusts(struct dcesrv_call_state *dce trusts->count = 0; r->out.trusts = trusts; - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx, - dce_call->conn->auth_state.session_info, 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + lp_ctx, + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { - return WERR_GENERAL_FAILURE; + return WERR_GEN_FAILURE; } if ((r->in.trust_flags & NETR_TRUST_FLAG_INBOUND) || @@ -2306,7 +3716,7 @@ static WERROR dcesrv_netr_DsrEnumerateDomainTrusts(struct dcesrv_call_state *dce ret = gendb_search_dn(sam_ctx, mem_ctx, NULL, &dom_res, dom_attrs); if (ret != 1) { - return WERR_GENERAL_FAILURE; + return WERR_GEN_FAILURE; } trusts->count = n + 1; @@ -2324,7 +3734,7 @@ static WERROR dcesrv_netr_DsrEnumerateDomainTrusts(struct dcesrv_call_state *dce NETR_TRUST_FLAG_PRIMARY; /* we are always the root domain for now */ trusts->array[n].parent_index = 0; - trusts->array[n].trust_type = NETR_TRUST_TYPE_UPLEVEL; + 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], @@ -2348,97 +3758,104 @@ static WERROR dcesrv_netr_DsrDeregisterDNSHostRecords(struct dcesrv_call_state * } +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) { - DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); -} - - -static WERROR fill_forest_trust_array(TALLOC_CTX *mem_ctx, - struct ldb_context *sam_ctx, - struct loadparm_context *lp_ctx, - struct lsa_ForestTrustInformation *info) -{ - struct lsa_ForestTrustDomainInfo *domain_info; - struct lsa_ForestTrustRecord *e; - struct ldb_message **dom_res; - const char * const dom_attrs[] = { "objectSid", NULL }; - int ret; - - /* we need to provide 2 entries: - * 1. the Root Forest name - * 2. the Domain Information - */ - - info->count = 2; - info->entries = talloc_array(info, struct lsa_ForestTrustRecord *, 2); - W_ERROR_HAVE_NO_MEMORY(info->entries); - - /* Forest root info */ - e = talloc(info, struct lsa_ForestTrustRecord); - W_ERROR_HAVE_NO_MEMORY(e); - - e->flags = 0; - e->type = LSA_FOREST_TRUST_TOP_LEVEL_NAME; - e->time = 0; /* so far always 0 in trces. */ - e->forest_trust_data.top_level_name.string = samdb_forest_name(sam_ctx, - mem_ctx); - W_ERROR_HAVE_NO_MEMORY(e->forest_trust_data.top_level_name.string); - - info->entries[0] = e; - - /* Domain info */ - e = talloc(info, struct lsa_ForestTrustRecord); - W_ERROR_HAVE_NO_MEMORY(e); - - /* get our own domain info */ - ret = gendb_search_dn(sam_ctx, mem_ctx, NULL, &dom_res, dom_attrs); - if (ret != 1) { - return WERR_GENERAL_FAILURE; - } + struct netr_ServerGetTrustInfo r2 = {}; + struct netr_TrustInfo *_ti = NULL; + NTSTATUS status; - /* TODO: check if disabled and set flags accordingly */ - e->flags = 0; - e->type = LSA_FOREST_TRUST_DOMAIN_INFO; - e->time = 0; /* so far always 0 in traces. */ + 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; - domain_info = &e->forest_trust_data.domain_info; - domain_info->domain_sid = samdb_result_dom_sid(info, dom_res[0], - "objectSid"); - domain_info->dns_domain_name.string = lpcfg_dnsdomain(lp_ctx); - domain_info->netbios_domain_name.string = lpcfg_workgroup(lp_ctx); + 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; - info->entries[1] = e; + status = dcesrv_netr_ServerGetTrustInfo(dce_call, mem_ctx, &r2); - talloc_free(dom_res); + 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 WERR_OK; + 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 loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; - struct lsa_ForestTrustInformation *info, **info_ptr; - struct ldb_context *sam_ctx; - WERROR werr; + struct dcesrv_connection *conn = dce_call->conn; + struct auth_session_info *session_info = conn->auth_state.session_info; + 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 = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx, - dce_call->conn->auth_state.session_info, 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + lp_ctx, + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { - return WERR_GENERAL_FAILURE; + 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) { @@ -2449,32 +3866,92 @@ static WERROR dcesrv_netr_DsRGetForestTrustInformation(struct dcesrv_call_state if (r->in.trusted_domain_name == NULL) { return WERR_INVALID_FLAGS; } - - /* TODO: establish an schannel connection with - * r->in.trusted_domain_name and perform a - * netr_GetForestTrustInformation call against it */ - - /* for now return not implementd */ - return WERR_CALL_NOT_IMPLEMENTED; } - /* TODO: check r->in.server_name is our name */ + if (r->in.trusted_domain_name == NULL) { + NTSTATUS status; - info_ptr = talloc(mem_ctx, struct lsa_ForestTrustInformation *); - W_ERROR_HAVE_NO_MEMORY(info_ptr); + /* + * 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); + } - info = talloc_zero(info_ptr, struct lsa_ForestTrustInformation); - W_ERROR_HAVE_NO_MEMORY(info); + return WERR_OK; + } - werr = fill_forest_trust_array(mem_ctx, sam_ctx, lp_ctx, info); - W_ERROR_NOT_OK_RETURN(werr); + /* + * 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, + state->dce_call->msg_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; + } - *info_ptr = info; - r->out.forest_trust_info = info_ptr; + /* + * 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 @@ -2484,11 +3961,13 @@ static NTSTATUS dcesrv_netr_GetForestTrustInformation(struct dcesrv_call_state * struct netr_GetForestTrustInformation *r) { struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; - struct netlogon_creds_CredentialState *creds; - struct lsa_ForestTrustInformation *info, **info_ptr; - struct ldb_context *sam_ctx; + 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; - WERROR werr; status = dcesrv_netr_creds_server_step_check(dce_call, mem_ctx, @@ -2505,30 +3984,43 @@ static NTSTATUS dcesrv_netr_GetForestTrustInformation(struct dcesrv_call_state * return NT_STATUS_NOT_IMPLEMENTED; } - sam_ctx = samdb_connect(mem_ctx, dce_call->event_ctx, lp_ctx, - dce_call->conn->auth_state.session_info, 0); + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + lp_ctx, + dce_call->conn->auth_state.session_info, + dce_call->conn->remote_address, + 0); if (sam_ctx == NULL) { return NT_STATUS_INTERNAL_ERROR; } /* TODO: check r->in.server_name is our name */ - info_ptr = talloc(mem_ctx, struct lsa_ForestTrustInformation *); - if (!info_ptr) { - return NT_STATUS_NO_MEMORY; + domain_dn = ldb_get_default_basedn(sam_ctx); + if (domain_dn == NULL) { + return NT_STATUS_INTERNAL_ERROR; } - info = talloc_zero(info_ptr, struct lsa_ForestTrustInformation); - if (!info) { - return NT_STATUS_NO_MEMORY; + + forest_dn = ldb_get_root_basedn(sam_ctx); + if (forest_dn == NULL) { + return NT_STATUS_INTERNAL_ERROR; } - werr = fill_forest_trust_array(mem_ctx, sam_ctx, lp_ctx, info); - if (!W_ERROR_IS_OK(werr)) { - return werror_to_ntstatus(werr); + 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; } - *info_ptr = info; - r->out.forest_trust_info = info_ptr; + 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; } @@ -2540,7 +4032,162 @@ static NTSTATUS dcesrv_netr_GetForestTrustInformation(struct dcesrv_call_state * static NTSTATUS dcesrv_netr_ServerGetTrustInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct netr_ServerGetTrustInfo *r) { - DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); + 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 = samdb_connect(mem_ctx, + dce_call->event_ctx, + lp_ctx, + system_session(lp_ctx), + dce_call->conn->remote_address, + 0); + 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], + NULL, &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; + netlogon_creds_des_encrypt(creds, r->out.new_owf_password); + } + if (prevNtHash != NULL) { + *r->out.old_owf_password = *prevNtHash; + netlogon_creds_des_encrypt(creds, r->out.old_owf_password); + } + + if (trust_info != NULL) { + *r->out.trust_info = trust_info; + } + + return NT_STATUS_OK; } /*