diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/winbindd/winbindd_cm.c | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/winbindd/winbindd_cm.c')
-rw-r--r-- | source3/winbindd/winbindd_cm.c | 3434 |
1 files changed, 3434 insertions, 0 deletions
diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c new file mode 100644 index 0000000..1685edb --- /dev/null +++ b/source3/winbindd/winbindd_cm.c @@ -0,0 +1,3434 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon connection manager + + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett 2002 + Copyright (C) Gerald (Jerry) Carter 2003-2005. + Copyright (C) Volker Lendecke 2004-2005 + Copyright (C) Jeremy Allison 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + We need to manage connections to domain controllers without having to + mess up the main winbindd code with other issues. The aim of the + connection manager is to: + + - make connections to domain controllers and cache them + - re-establish connections when networks or servers go down + - centralise the policy on connection timeouts, domain controller + selection etc + - manage re-entrancy for when winbindd becomes able to handle + multiple outstanding rpc requests + + Why not have connection management as part of the rpc layer like tng? + Good question. This code may morph into libsmb/rpc_cache.c or something + like that but at the moment it's simply staying as part of winbind. I + think the TNG architecture of forcing every user of the rpc layer to use + the connection caching system is a bad idea. It should be an optional + method of using the routines. + + The TNG design is quite good but I disagree with some aspects of the + implementation. -tpot + + */ + +/* + TODO: + + - I'm pretty annoyed by all the make_nmb_name() stuff. It should be + moved down into another function. + + - Take care when destroying cli_structs as they can be shared between + various sam handles. + + */ + +#include "includes.h" +#include "winbindd.h" +#include "libsmb/namequery.h" +#include "../libcli/auth/libcli_auth.h" +#include "../librpc/gen_ndr/ndr_netlogon_c.h" +#include "rpc_client/cli_pipe.h" +#include "rpc_client/cli_netlogon.h" +#include "../librpc/gen_ndr/ndr_samr_c.h" +#include "../librpc/gen_ndr/ndr_lsa_c.h" +#include "rpc_client/cli_lsarpc.h" +#include "../librpc/gen_ndr/ndr_dssetup_c.h" +#include "libads/sitename_cache.h" +#include "libsmb/libsmb.h" +#include "libsmb/clidgram.h" +#include "ads.h" +#include "secrets.h" +#include "../libcli/security/security.h" +#include "passdb.h" +#include "messages.h" +#include "auth/gensec/gensec.h" +#include "../libcli/smb/smbXcli_base.h" +#include "libcli/auth/netlogon_creds_cli.h" +#include "auth.h" +#include "rpc_server/rpc_ncacn_np.h" +#include "auth/credentials/credentials.h" +#include "lib/param/param.h" +#include "lib/gencache.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +struct dc_name_ip { + fstring name; + struct sockaddr_storage ss; +}; + +extern struct winbindd_methods reconnect_methods; + +static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain, bool need_rw_dc); +static void set_dc_type_and_flags( struct winbindd_domain *domain ); +static bool set_dc_type_and_flags_trustinfo( struct winbindd_domain *domain ); +static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, + struct dc_name_ip **dcs, int *num_dcs, + uint32_t request_flags); + +void winbind_msg_domain_offline(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + const char *domain_name = (const char *)data->data; + struct winbindd_domain *domain; + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + DBG_DEBUG("Domain %s not found!\n", domain_name); + return; + } + + DBG_DEBUG("Domain %s was %s, change to offline now.\n", + domain_name, + domain->online ? "online" : "offline"); + + domain->online = false; +} + +void winbind_msg_domain_online(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + const char *domain_name = (const char *)data->data; + struct winbindd_domain *domain; + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + return; + } + + SMB_ASSERT(wb_child_domain() == NULL); + + DBG_DEBUG("Domain %s was %s, marking as online now!\n", + domain_name, + domain->online ? "online" : "offline"); + + domain->online = true; +} + +/**************************************************************** + Set domain offline and also add handler to put us back online + if we detect a DC. +****************************************************************/ + +void set_domain_offline(struct winbindd_domain *domain) +{ + pid_t parent_pid = getppid(); + + DEBUG(10,("set_domain_offline: called for domain %s\n", + domain->name )); + + if (domain->internal) { + DEBUG(3,("set_domain_offline: domain %s is internal - logic error.\n", + domain->name )); + return; + } + + domain->online = False; + + /* Offline domains are always initialized. They're + re-initialized when they go back online. */ + + domain->initialized = True; + + /* Send a message to the parent that the domain is offline. */ + if (parent_pid > 1 && !domain->internal) { + messaging_send_buf(global_messaging_context(), + pid_to_procid(parent_pid), + MSG_WINBIND_DOMAIN_OFFLINE, + (uint8_t *)domain->name, + strlen(domain->name) + 1); + } + + /* Send an offline message to the idmap child when our + primary domain goes offline */ + if ( domain->primary ) { + pid_t idmap_pid = idmap_child_pid(); + + if (idmap_pid != 0) { + messaging_send_buf(global_messaging_context(), + pid_to_procid(idmap_pid), + MSG_WINBIND_OFFLINE, + (const uint8_t *)domain->name, + strlen(domain->name)+1); + } + } + + return; +} + +/**************************************************************** + Set domain online - if allowed. +****************************************************************/ + +static void set_domain_online(struct winbindd_domain *domain) +{ + pid_t parent_pid = getppid(); + + DEBUG(10,("set_domain_online: called for domain %s\n", + domain->name )); + + if (domain->internal) { + DEBUG(3,("set_domain_online: domain %s is internal - logic error.\n", + domain->name )); + return; + } + + if (get_global_winbindd_state_offline()) { + DEBUG(10,("set_domain_online: domain %s remaining globally offline\n", + domain->name )); + return; + } + + winbindd_set_locator_kdc_envs(domain); + + /* If we are waiting to get a krb5 ticket, trigger immediately. */ + ccache_regain_all_now(); + + /* Ok, we're out of any startup mode now... */ + domain->startup = False; + + if (domain->online == False) { + /* We were offline - now we're online. We default to + using the MS-RPC backend if we started offline, + and if we're going online for the first time we + should really re-initialize the backends and the + checks to see if we're talking to an AD or NT domain. + */ + + domain->initialized = False; + + /* 'reconnect_methods' is the MS-RPC backend. */ + if (domain->backend == &reconnect_methods) { + domain->backend = NULL; + } + } + + domain->online = True; + + /* Send a message to the parent that the domain is online. */ + if (parent_pid > 1 && !domain->internal) { + messaging_send_buf(global_messaging_context(), + pid_to_procid(parent_pid), + MSG_WINBIND_DOMAIN_ONLINE, + (uint8_t *)domain->name, + strlen(domain->name) + 1); + } + + /* Send an online message to the idmap child when our + primary domain comes online */ + + if ( domain->primary ) { + pid_t idmap_pid = idmap_child_pid(); + + if (idmap_pid != 0) { + messaging_send_buf(global_messaging_context(), + pid_to_procid(idmap_pid), + MSG_WINBIND_ONLINE, + (const uint8_t *)domain->name, + strlen(domain->name)+1); + } + } + + return; +} + +/**************************************************************** + Requested to set a domain online. +****************************************************************/ + +void set_domain_online_request(struct winbindd_domain *domain) +{ + NTSTATUS status; + + SMB_ASSERT(wb_child_domain() || idmap_child()); + + DEBUG(10,("set_domain_online_request: called for domain %s\n", + domain->name )); + + if (get_global_winbindd_state_offline()) { + DEBUG(10,("set_domain_online_request: domain %s remaining globally offline\n", + domain->name )); + return; + } + + if (domain->internal) { + DEBUG(10, ("set_domain_online_request: Internal domains are " + "always online\n")); + return; + } + + /* + * This call takes care of setting the online flag to true if we + * connected, or tell the parent to ping us back if false. Bypasses + * online check so always does network calls. + */ + status = init_dc_connection_network(domain, true); + DBG_DEBUG("init_dc_connection_network(), returned %s, called for " + "domain %s (online = %s)\n", + nt_errstr(status), + domain->name, + domain->online ? "true" : "false"); +} + +/**************************************************************** + Add -ve connection cache entries for domain and realm. +****************************************************************/ + +static void winbind_add_failed_connection_entry( + const struct winbindd_domain *domain, + const char *server, + NTSTATUS result) +{ + add_failed_connection_entry(domain->name, server, result); + /* If this was the saf name for the last thing we talked to, + remove it. */ + saf_delete(domain->name); + if (domain->alt_name != NULL) { + add_failed_connection_entry(domain->alt_name, server, result); + saf_delete(domain->alt_name); + } + winbindd_unset_locator_kdc_env(domain); +} + +/* Choose between anonymous or authenticated connections. We need to use + an authenticated connection if DCs have the RestrictAnonymous registry + entry set > 0, or the "Additional restrictions for anonymous + connections" set in the win2k Local Security Policy. + + Caller to free() result in domain, username, password +*/ + +static void cm_get_ipc_userpass(char **username, char **domain, char **password) +{ + *username = (char *)secrets_fetch(SECRETS_AUTH_USER, NULL); + *domain = (char *)secrets_fetch(SECRETS_AUTH_DOMAIN, NULL); + *password = (char *)secrets_fetch(SECRETS_AUTH_PASSWORD, NULL); + + if (*username && **username) { + + if (!*domain || !**domain) + *domain = smb_xstrdup(lp_workgroup()); + + if (!*password || !**password) + *password = smb_xstrdup(""); + + DEBUG(3, ("cm_get_ipc_userpass: Retrieved auth-user from secrets.tdb [%s\\%s]\n", + *domain, *username)); + + } else { + DEBUG(3, ("cm_get_ipc_userpass: No auth-user defined\n")); + *username = smb_xstrdup(""); + *domain = smb_xstrdup(""); + *password = smb_xstrdup(""); + } +} + +static NTSTATUS cm_get_ipc_credentials(TALLOC_CTX *mem_ctx, + struct cli_credentials **_creds) +{ + + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + struct loadparm_context *lp_ctx; + char *username = NULL; + char *netbios_domain = NULL; + char *password = NULL; + struct cli_credentials *creds = NULL; + bool ok; + + cm_get_ipc_userpass(&username, &netbios_domain, &password); + + lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers()); + if (lp_ctx == NULL) { + DEBUG(1, ("loadparm_init_s3 failed\n")); + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + creds = cli_credentials_init(mem_ctx); + if (creds == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + ok = cli_credentials_set_conf(creds, lp_ctx); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + cli_credentials_set_kerberos_state(creds, + CRED_USE_KERBEROS_DISABLED, + CRED_SPECIFIED); + + ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + ok = cli_credentials_set_username(creds, username, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + ok = cli_credentials_set_password(creds, password, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + *_creds = creds; + creds = NULL; + status = NT_STATUS_OK; + fail: + TALLOC_FREE(creds); + SAFE_FREE(username); + SAFE_FREE(netbios_domain); + SAFE_FREE(password); + TALLOC_FREE(frame); + return status; +} + +static bool cm_is_ipc_credentials(struct cli_credentials *creds) +{ + TALLOC_CTX *frame = talloc_stackframe(); + char *ipc_account = NULL; + char *ipc_domain = NULL; + char *ipc_password = NULL; + const char *creds_account = NULL; + const char *creds_domain = NULL; + const char *creds_password = NULL; + bool ret = false; + + cm_get_ipc_userpass(&ipc_account, &ipc_domain, &ipc_password); + + creds_account = cli_credentials_get_username(creds); + creds_domain = cli_credentials_get_domain(creds); + creds_password = cli_credentials_get_password(creds); + + if (!strequal(ipc_domain, creds_domain)) { + goto done; + } + + if (!strequal(ipc_account, creds_account)) { + goto done; + } + + if (!strcsequal(ipc_password, creds_password)) { + goto done; + } + + ret = true; + done: + SAFE_FREE(ipc_account); + SAFE_FREE(ipc_domain); + SAFE_FREE(ipc_password); + TALLOC_FREE(frame); + return ret; +} + +static bool get_dc_name_via_netlogon(struct winbindd_domain *domain, + fstring dcname, + struct sockaddr_storage *dc_ss, + uint32_t request_flags) +{ + struct winbindd_domain *our_domain = NULL; + struct rpc_pipe_client *netlogon_pipe = NULL; + NTSTATUS result; + WERROR werr; + TALLOC_CTX *mem_ctx; + unsigned int orig_timeout; + const char *tmp = NULL; + const char *p; + struct dcerpc_binding_handle *b; + + /* Hmmmm. We can only open one connection to the NETLOGON pipe at the + * moment.... */ + + if (IS_DC) { + return False; + } + + if (domain->primary) { + return False; + } + + our_domain = find_our_domain(); + + if ((mem_ctx = talloc_init("get_dc_name_via_netlogon")) == NULL) { + return False; + } + + result = cm_connect_netlogon(our_domain, &netlogon_pipe); + if (!NT_STATUS_IS_OK(result)) { + talloc_destroy(mem_ctx); + return False; + } + + b = netlogon_pipe->binding_handle; + + /* This call can take a long time - allow the server to time out. + 35 seconds should do it. */ + + orig_timeout = rpccli_set_timeout(netlogon_pipe, 35000); + + if (our_domain->active_directory) { + struct netr_DsRGetDCNameInfo *domain_info = NULL; + + /* + * TODO request flags are not respected in the server + * (and in some cases, like REQUIRE_PDC, causes an error) + */ + result = dcerpc_netr_DsRGetDCName(b, + mem_ctx, + our_domain->dcname, + domain->name, + NULL, + NULL, + request_flags|DS_RETURN_DNS_NAME, + &domain_info, + &werr); + if (NT_STATUS_IS_OK(result) && W_ERROR_IS_OK(werr)) { + tmp = talloc_strdup( + mem_ctx, domain_info->dc_unc); + if (tmp == NULL) { + DEBUG(0, ("talloc_strdup failed\n")); + talloc_destroy(mem_ctx); + return false; + } + if (domain->alt_name == NULL) { + domain->alt_name = talloc_strdup(domain, + domain_info->domain_name); + if (domain->alt_name == NULL) { + DEBUG(0, ("talloc_strdup failed\n")); + talloc_destroy(mem_ctx); + return false; + } + } + if (domain->forest_name == NULL) { + domain->forest_name = talloc_strdup(domain, + domain_info->forest_name); + if (domain->forest_name == NULL) { + DEBUG(0, ("talloc_strdup failed\n")); + talloc_destroy(mem_ctx); + return false; + } + } + } + } else { + result = dcerpc_netr_GetAnyDCName(b, mem_ctx, + our_domain->dcname, + domain->name, + &tmp, + &werr); + } + + /* And restore our original timeout. */ + rpccli_set_timeout(netlogon_pipe, orig_timeout); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("dcerpc_netr_GetAnyDCName failed: %s\n", + nt_errstr(result))); + talloc_destroy(mem_ctx); + return false; + } + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(10,("dcerpc_netr_GetAnyDCName failed: %s\n", + win_errstr(werr))); + talloc_destroy(mem_ctx); + return false; + } + + /* dcerpc_netr_GetAnyDCName gives us a name with \\ */ + p = strip_hostname(tmp); + + fstrcpy(dcname, p); + + talloc_destroy(mem_ctx); + + DEBUG(10,("dcerpc_netr_GetAnyDCName returned %s\n", dcname)); + + if (!resolve_name(dcname, dc_ss, 0x20, true)) { + return False; + } + + return True; +} + +/** + * Helper function to assemble trust password and account name + */ +static NTSTATUS get_trust_credentials(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + bool netlogon, + struct cli_credentials **_creds) +{ + const struct winbindd_domain *creds_domain = NULL; + struct cli_credentials *creds; + NTSTATUS status; + bool force_machine_account = false; + + /* If we are a DC and this is not our own domain */ + + if (!domain->active_directory) { + if (!netlogon) { + /* + * For non active directory domains + * we can only use NTLMSSP for SMB. + * + * But the trust account is not allowed + * to use SMB with NTLMSSP. + */ + force_machine_account = true; + } + } + + if (IS_DC && !force_machine_account) { + creds_domain = domain; + } else { + creds_domain = find_our_domain(); + if (creds_domain == NULL) { + return NT_STATUS_INVALID_SERVER_STATE; + } + } + + status = pdb_get_trust_credentials(creds_domain->name, + creds_domain->alt_name, + mem_ctx, + &creds); + if (!NT_STATUS_IS_OK(status)) { + goto ipc_fallback; + } + + if (creds_domain != domain) { + /* + * We can only use schannel against a direct trust + */ + cli_credentials_set_secure_channel_type(creds, + SEC_CHAN_NULL); + } + + *_creds = creds; + return NT_STATUS_OK; + + ipc_fallback: + if (netlogon) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + status = cm_get_ipc_credentials(mem_ctx, &creds); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *_creds = creds; + return NT_STATUS_OK; +} + +/************************************************************************ + Given a fd with a just-connected TCP connection to a DC, open a connection + to the pipe. +************************************************************************/ + +static NTSTATUS cm_prepare_connection(struct winbindd_domain *domain, + const int sockfd, + const char *controller, + struct cli_state **cli, + bool *retry) +{ + bool try_ipc_auth = false; + const char *machine_principal = NULL; + const char *machine_realm = NULL; + const char *machine_account = NULL; + const char *machine_domain = NULL; + int flags = 0; + struct cli_credentials *creds = NULL; + + struct named_mutex *mutex; + + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + NTSTATUS tmp_status; + NTSTATUS tcon_status = NT_STATUS_NETWORK_NAME_DELETED; + + enum smb_signing_setting smb_sign_client_connections = lp_client_ipc_signing(); + + if (IS_DC) { + if (domain->secure_channel_type == SEC_CHAN_NULL) { + /* + * Make sure we don't even try to + * connect to a foreign domain + * without a direct outbound trust. + */ + close(sockfd); + return NT_STATUS_NO_TRUST_LSA_SECRET; + } + + /* + * As AD DC we only use netlogon and lsa + * using schannel over an anonymous transport + * (ncacn_ip_tcp or ncacn_np). + * + * Currently we always establish the SMB connection, + * even if we don't use it, because we later use ncacn_ip_tcp. + * + * As we won't use the SMB connection there's no + * need to try kerberos. And NT4 domains expect + * an anonymous IPC$ connection anyway. + */ + smb_sign_client_connections = SMB_SIGNING_OFF; + } + + if (smb_sign_client_connections == SMB_SIGNING_DEFAULT) { + /* + * If we are connecting to our own AD domain, require + * smb signing to disrupt MITM attacks + */ + if (domain->primary && lp_security() == SEC_ADS) { + smb_sign_client_connections = SMB_SIGNING_REQUIRED; + /* + * If we are in or are an AD domain and connecting to another + * AD domain in our forest + * then require smb signing to disrupt MITM attacks + */ + } else if ((lp_security() == SEC_ADS) + && domain->active_directory + && (domain->domain_trust_attribs + & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST)) { + smb_sign_client_connections = SMB_SIGNING_REQUIRED; + } + } + + DEBUG(10,("cm_prepare_connection: connecting to DC %s for domain %s\n", + controller, domain->name )); + + *retry = True; + + mutex = grab_named_mutex(talloc_tos(), controller, + WINBIND_SERVER_MUTEX_WAIT_TIME); + if (mutex == NULL) { + close(sockfd); + DEBUG(0,("cm_prepare_connection: mutex grab failed for %s\n", + controller)); + result = NT_STATUS_POSSIBLE_DEADLOCK; + goto done; + } + + /* + * cm_prepare_connection() is responsible that sockfd does not leak. + * Once cli_state_create() returns with success, the + * smbXcli_conn_destructor() makes sure that close(sockfd) is finally + * called. Till that, close(sockfd) must be called on every unsuccessful + * return. + */ + *cli = cli_state_create(NULL, sockfd, controller, + smb_sign_client_connections, flags); + if (*cli == NULL) { + close(sockfd); + DEBUG(1, ("Could not cli_initialize\n")); + result = NT_STATUS_NO_MEMORY; + goto done; + } + + cli_set_timeout(*cli, 10000); /* 10 seconds */ + + set_socket_options(sockfd, lp_socket_options()); + + result = smbXcli_negprot((*cli)->conn, + (*cli)->timeout, + lp_client_ipc_min_protocol(), + lp_client_ipc_max_protocol(), + NULL, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("cli_negprot failed: %s\n", nt_errstr(result))); + goto done; + } + + if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_NT1 && + smb1cli_conn_capabilities((*cli)->conn) & CAP_EXTENDED_SECURITY) { + try_ipc_auth = true; + } else if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_SMB2_02) { + try_ipc_auth = true; + } else if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) { + /* + * If we are forcing on SMB signing, then we must + * require authentication unless this is a one-way + * trust, and we have no stored user/password + */ + try_ipc_auth = true; + } + + if (IS_DC) { + /* + * As AD DC we only use netlogon and lsa + * using schannel over an anonymous transport + * (ncacn_ip_tcp or ncacn_np). + * + * Currently we always establish the SMB connection, + * even if we don't use it, because we later use ncacn_ip_tcp. + * + * As we won't use the SMB connection there's no + * need to try kerberos. And NT4 domains expect + * an anonymous IPC$ connection anyway. + */ + try_ipc_auth = false; + } + + if (try_ipc_auth) { + result = get_trust_credentials(domain, talloc_tos(), false, &creds); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("get_trust_credentials(%s) failed: %s\n", + domain->name, nt_errstr(result))); + goto done; + } + } else { + /* + * Without SPNEGO or NTLMSSP (perhaps via SMB2) we + * would try and authentication with our machine + * account password and fail. This is very rare in + * the modern world however + */ + creds = cli_credentials_init_anon(talloc_tos()); + if (creds == NULL) { + result = NT_STATUS_NO_MEMORY; + DEBUG(1, ("cli_credentials_init_anon(%s) failed: %s\n", + domain->name, nt_errstr(result))); + goto done; + } + } + + machine_principal = cli_credentials_get_principal(creds, + talloc_tos()); + machine_realm = cli_credentials_get_realm(creds); + machine_account = cli_credentials_get_username(creds); + machine_domain = cli_credentials_get_domain(creds); + + DEBUG(5, ("connecting to %s (%s, %s) with account [%s\\%s] principal " + "[%s] and realm [%s]\n", + controller, domain->name, domain->alt_name, + machine_domain, machine_account, + machine_principal, machine_realm)); + + if (cli_credentials_is_anonymous(creds)) { + goto anon_fallback; + } + + winbindd_set_locator_kdc_envs(domain); + + result = cli_session_setup_creds(*cli, creds); + if (NT_STATUS_IS_OK(result)) { + goto session_setup_done; + } + + DEBUG(1, ("authenticated session setup to %s using %s failed with %s\n", + controller, + cli_credentials_get_unparsed_name(creds, talloc_tos()), + nt_errstr(result))); + + /* + * If we are not going to validate the connection + * with SMB signing, then allow us to fall back to + * anonymous + */ + if (NT_STATUS_EQUAL(result, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT) + || NT_STATUS_EQUAL(result, NT_STATUS_TRUSTED_DOMAIN_FAILURE) + || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_ACCOUNT_NAME) + || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_COMPUTER_NAME) + || NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_DOMAIN) + || NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) + || NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE)) + { + if (!cm_is_ipc_credentials(creds)) { + goto ipc_fallback; + } + + if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) { + goto done; + } + + goto anon_fallback; + } + + goto done; + + ipc_fallback: + TALLOC_FREE(creds); + tmp_status = cm_get_ipc_credentials(talloc_tos(), &creds); + if (!NT_STATUS_IS_OK(tmp_status)) { + result = tmp_status; + goto done; + } + + if (cli_credentials_is_anonymous(creds)) { + goto anon_fallback; + } + + machine_account = cli_credentials_get_username(creds); + machine_domain = cli_credentials_get_domain(creds); + + DEBUG(5, ("connecting to %s from %s using NTLMSSP with username " + "[%s]\\[%s]\n", controller, lp_netbios_name(), + machine_domain, machine_account)); + + result = cli_session_setup_creds(*cli, creds); + if (NT_STATUS_IS_OK(result)) { + goto session_setup_done; + } + + DEBUG(1, ("authenticated session setup to %s using %s failed with %s\n", + controller, + cli_credentials_get_unparsed_name(creds, talloc_tos()), + nt_errstr(result))); + + /* + * If we are not going to validate the connection + * with SMB signing, then allow us to fall back to + * anonymous + */ + if (NT_STATUS_EQUAL(result, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT) + || NT_STATUS_EQUAL(result, NT_STATUS_TRUSTED_DOMAIN_FAILURE) + || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_ACCOUNT_NAME) + || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_COMPUTER_NAME) + || NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_DOMAIN) + || NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) + || NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE)) + { + goto anon_fallback; + } + + goto done; + + anon_fallback: + TALLOC_FREE(creds); + + if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) { + goto done; + } + + /* Fall back to anonymous connection, this might fail later */ + DEBUG(5,("cm_prepare_connection: falling back to anonymous " + "connection for DC %s\n", + controller )); + + result = cli_session_setup_anon(*cli); + if (NT_STATUS_IS_OK(result)) { + DEBUG(5, ("Connected anonymously\n")); + goto session_setup_done; + } + + DEBUG(1, ("anonymous session setup to %s failed with %s\n", + controller, nt_errstr(result))); + + /* We can't session setup */ + goto done; + + session_setup_done: + TALLOC_FREE(creds); + + /* + * This should be a short term hack until + * dynamic re-authentication is implemented. + * + * See Bug 9175 - winbindd doesn't recover from + * NT_STATUS_NETWORK_SESSION_EXPIRED + */ + if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_SMB2_02) { + smbXcli_session_set_disconnect_expired((*cli)->smb2.session); + } + + result = cli_tree_connect(*cli, "IPC$", "IPC", NULL); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1,("failed tcon_X with %s\n", nt_errstr(result))); + goto done; + } + tcon_status = result; + + /* cache the server name for later connections */ + + saf_store(domain->name, controller); + if (domain->alt_name) { + saf_store(domain->alt_name, controller); + } + + winbindd_set_locator_kdc_envs(domain); + + TALLOC_FREE(mutex); + *retry = False; + + result = NT_STATUS_OK; + + done: + TALLOC_FREE(mutex); + TALLOC_FREE(creds); + + if (NT_STATUS_IS_OK(result)) { + result = tcon_status; + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("Failed to prepare SMB connection to %s: %s\n", + controller, nt_errstr(result))); + winbind_add_failed_connection_entry(domain, controller, result); + if ((*cli) != NULL) { + cli_shutdown(*cli); + *cli = NULL; + } + } + + return result; +} + +/******************************************************************* + Add a dcname and sockaddr_storage pair to the end of a dc_name_ip + array. + + Keeps the list unique by not adding duplicate entries. + + @param[in] mem_ctx talloc memory context to allocate from + @param[in] domain_name domain of the DC + @param[in] dcname name of the DC to add to the list + @param[in] pss Internet address and port pair to add to the list + @param[in,out] dcs array of dc_name_ip structures to add to + @param[in,out] num_dcs number of dcs returned in the dcs array + @return true if the list was added to, false otherwise +*******************************************************************/ + +static bool add_one_dc_unique(TALLOC_CTX *mem_ctx, const char *domain_name, + const char *dcname, struct sockaddr_storage *pss, + struct dc_name_ip **dcs, int *num) +{ + int i = 0; + + if (!NT_STATUS_IS_OK(check_negative_conn_cache(domain_name, dcname))) { + DEBUG(10, ("DC %s was in the negative conn cache\n", dcname)); + return False; + } + + /* Make sure there's no duplicates in the list */ + for (i=0; i<*num; i++) + if (sockaddr_equal( + (struct sockaddr *)(void *)&(*dcs)[i].ss, + (struct sockaddr *)(void *)pss)) + return False; + + *dcs = talloc_realloc(mem_ctx, *dcs, struct dc_name_ip, (*num)+1); + + if (*dcs == NULL) + return False; + + fstrcpy((*dcs)[*num].name, dcname); + (*dcs)[*num].ss = *pss; + *num += 1; + return True; +} + +static bool add_sockaddr_to_array(TALLOC_CTX *mem_ctx, + struct sockaddr_storage *pss, uint16_t port, + struct sockaddr_storage **addrs, int *num) +{ + *addrs = talloc_realloc(mem_ctx, *addrs, struct sockaddr_storage, (*num)+1); + + if (*addrs == NULL) { + *num = 0; + return False; + } + + (*addrs)[*num] = *pss; + set_sockaddr_port((struct sockaddr *)&(*addrs)[*num], port); + + *num += 1; + return True; +} + +#ifdef HAVE_ADS +static bool dcip_check_name_ads(const struct winbindd_domain *domain, + struct samba_sockaddr *sa, + uint32_t request_flags, + TALLOC_CTX *mem_ctx, + char **namep) +{ + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + char *name = NULL; + ADS_STRUCT *ads = NULL; + ADS_STATUS ads_status; + char addr[INET6_ADDRSTRLEN]; + + print_sockaddr(addr, sizeof(addr), &sa->u.ss); + D_DEBUG("Trying to figure out the DC name for domain '%s' at IP '%s'.\n", + domain->name, + addr); + + ads = ads_init(tmp_ctx, + domain->alt_name, + domain->name, + addr, + ADS_SASL_PLAIN); + if (ads == NULL) { + ads_status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + goto out; + } + ads->auth.flags |= ADS_AUTH_NO_BIND; + ads->config.flags |= request_flags; + ads->server.no_fallback = true; + + ads_status = ads_connect(ads); + if (!ADS_ERR_OK(ads_status)) { + goto out; + } + + /* We got a cldap packet. */ + name = talloc_strdup(tmp_ctx, ads->config.ldap_server_name); + if (name == NULL) { + ads_status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + goto out; + } + namecache_store(name, 0x20, 1, sa); + + DBG_DEBUG("CLDAP flags = 0x%"PRIx32"\n", ads->config.flags); + + if (domain->primary && (ads->config.flags & NBT_SERVER_KDC)) { + if (ads_closest_dc(ads)) { + char *sitename = sitename_fetch(tmp_ctx, + ads->config.realm); + + /* We're going to use this KDC for this realm/domain. + If we are using sites, then force the krb5 libs + to use this KDC. */ + + create_local_private_krb5_conf_for_domain(domain->alt_name, + domain->name, + sitename, + &sa->u.ss); + + TALLOC_FREE(sitename); + } else { + /* use an off site KDC */ + create_local_private_krb5_conf_for_domain(domain->alt_name, + domain->name, + NULL, + &sa->u.ss); + } + winbindd_set_locator_kdc_envs(domain); + + /* Ensure we contact this DC also. */ + saf_store(domain->name, name); + saf_store(domain->alt_name, name); + } + + D_DEBUG("DC name for domain '%s' at IP '%s' is '%s'\n", + domain->name, + addr, + name); + *namep = talloc_move(mem_ctx, &name); + +out: + TALLOC_FREE(tmp_ctx); + + return ADS_ERR_OK(ads_status) ? true : false; +} +#endif + +/******************************************************************* + convert an ip to a name + For an AD Domain, it checks the requirements of the request flags. +*******************************************************************/ + +static bool dcip_check_name(TALLOC_CTX *mem_ctx, + const struct winbindd_domain *domain, + struct sockaddr_storage *pss, + char **name, uint32_t request_flags) +{ + struct samba_sockaddr sa = {0}; + uint32_t nt_version = NETLOGON_NT_VERSION_1; + NTSTATUS status; + const char *dc_name; + fstring nbtname; +#ifdef HAVE_ADS + bool is_ad_domain = false; +#endif + bool ok = sockaddr_storage_to_samba_sockaddr(&sa, pss); + if (!ok) { + return false; + } + +#ifdef HAVE_ADS + /* For active directory servers, try to get the ldap server name. + None of these failures should be considered critical for now */ + + if ((lp_security() == SEC_ADS) && (domain->alt_name != NULL)) { + is_ad_domain = true; + } else if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + is_ad_domain = domain->active_directory; + } + + if (is_ad_domain) { + return dcip_check_name_ads(domain, + &sa, + request_flags, + mem_ctx, + name); + } +#endif + + { + size_t len = strlen(lp_netbios_name()); + char my_acct_name[len+2]; + + snprintf(my_acct_name, + sizeof(my_acct_name), + "%s$", + lp_netbios_name()); + + status = nbt_getdc(global_messaging_context(), 10, &sa.u.ss, + domain->name, &domain->sid, + my_acct_name, ACB_WSTRUST, + nt_version, mem_ctx, &nt_version, + &dc_name, NULL); + } + if (NT_STATUS_IS_OK(status)) { + *name = talloc_strdup(mem_ctx, dc_name); + if (*name == NULL) { + return false; + } + namecache_store(*name, 0x20, 1, &sa); + return True; + } + + /* try node status request */ + + if (name_status_find(domain->name, 0x1c, 0x20, &sa.u.ss, nbtname) ) { + namecache_store(nbtname, 0x20, 1, &sa); + + if (name != NULL) { + *name = talloc_strdup(mem_ctx, nbtname); + if (*name == NULL) { + return false; + } + } + + return true; + } + return False; +} + +/******************************************************************* + Retrieve a list of IP addresses for domain controllers. + + The array is sorted in the preferred connection order. + + @param[in] mem_ctx talloc memory context to allocate from + @param[in] domain domain to retrieve DCs for + @param[out] dcs array of dcs that will be returned + @param[out] num_dcs number of dcs returned in the dcs array + @return always true +*******************************************************************/ + +static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, + struct dc_name_ip **dcs, int *num_dcs, + uint32_t request_flags) +{ + fstring dcname; + struct sockaddr_storage ss; + struct samba_sockaddr *sa_list = NULL; + size_t salist_size = 0; + size_t i; + bool is_our_domain; + enum security_types sec = (enum security_types)lp_security(); + + is_our_domain = strequal(domain->name, lp_workgroup()); + + /* If not our domain, get the preferred DC, by asking our primary DC */ + if ( !is_our_domain + && get_dc_name_via_netlogon(domain, dcname, &ss, request_flags) + && add_one_dc_unique(mem_ctx, domain->name, dcname, &ss, dcs, + num_dcs) ) + { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), &ss); + DEBUG(10, ("Retrieved DC %s at %s via netlogon\n", + dcname, addr)); + return True; + } + + if ((sec == SEC_ADS) && (domain->alt_name != NULL)) { + char *sitename = NULL; + + /* We need to make sure we know the local site before + doing any DNS queries, as this will restrict the + get_sorted_dc_list() call below to only fetching + DNS records for the correct site. */ + + /* Find any DC to get the site record. + We deliberately don't care about the + return here. */ + + get_dc_name(domain->name, domain->alt_name, dcname, &ss); + + sitename = sitename_fetch(mem_ctx, domain->alt_name); + if (sitename) { + + /* Do the site-specific AD dns lookup first. */ + (void)get_sorted_dc_list(mem_ctx, + domain->alt_name, + sitename, + &sa_list, + &salist_size, + true); + + /* Add ips to the DC array. We don't look up the name + of the DC in this function, but we fill in the char* + of the ip now to make the failed connection cache + work */ + for ( i=0; i<salist_size; i++ ) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), + &sa_list[i].u.ss); + add_one_dc_unique(mem_ctx, + domain->name, + addr, + &sa_list[i].u.ss, + dcs, + num_dcs); + } + + TALLOC_FREE(sa_list); + TALLOC_FREE(sitename); + salist_size = 0; + } + + /* Now we add DCs from the main AD DNS lookup. */ + (void)get_sorted_dc_list(mem_ctx, + domain->alt_name, + NULL, + &sa_list, + &salist_size, + true); + + for ( i=0; i<salist_size; i++ ) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), + &sa_list[i].u.ss); + add_one_dc_unique(mem_ctx, + domain->name, + addr, + &sa_list[i].u.ss, + dcs, + num_dcs); + } + + TALLOC_FREE(sa_list); + salist_size = 0; + } + + /* Try standard netbios queries if no ADS and fall back to DNS queries + * if alt_name is available */ + if (*num_dcs == 0) { + (void)get_sorted_dc_list(mem_ctx, + domain->name, + NULL, + &sa_list, + &salist_size, + false); + if (salist_size == 0) { + if (domain->alt_name != NULL) { + (void)get_sorted_dc_list(mem_ctx, + domain->alt_name, + NULL, + &sa_list, + &salist_size, + true); + } + } + + for ( i=0; i<salist_size; i++ ) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), + &sa_list[i].u.ss); + add_one_dc_unique(mem_ctx, + domain->name, + addr, + &sa_list[i].u.ss, + dcs, + num_dcs); + } + + TALLOC_FREE(sa_list); + salist_size = 0; + } + + return True; +} + +static bool connect_preferred_dc(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + uint32_t request_flags, + int *fd) +{ + char *saf_servername = NULL; + NTSTATUS status; + bool ok; + + /* + * We have to check the server affinity cache here since later we select + * a DC based on response time and not preference. + */ + if (domain->force_dc) { + saf_servername = domain->dcname; + } else { + saf_servername = saf_fetch(mem_ctx, domain->name); + } + + /* + * Check the negative connection cache before talking to it. It going + * down may have triggered the reconnection. + */ + if (saf_servername != NULL) { + status = check_negative_conn_cache(domain->name, + saf_servername); + if (!NT_STATUS_IS_OK(status)) { + saf_servername = NULL; + } + } + + if (saf_servername != NULL) { + DBG_DEBUG("saf_servername is '%s' for domain %s\n", + saf_servername, domain->name); + + /* convert an ip address to a name */ + if (is_ipaddress(saf_servername)) { + ok = interpret_string_addr(&domain->dcaddr, + saf_servername, + AI_NUMERICHOST); + if (!ok) { + return false; + } + } else { + ok = resolve_name(saf_servername, + &domain->dcaddr, + 0x20, + true); + if (!ok) { + goto fail; + } + } + + TALLOC_FREE(domain->dcname); + ok = dcip_check_name(domain, + domain, + &domain->dcaddr, + &domain->dcname, + request_flags); + if (!ok) { + goto fail; + } + } + + if (domain->dcname == NULL) { + return false; + } + + status = check_negative_conn_cache(domain->name, domain->dcname); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + status = smbsock_connect(&domain->dcaddr, 0, + NULL, -1, NULL, -1, + fd, NULL, 10); + if (!NT_STATUS_IS_OK(status)) { + winbind_add_failed_connection_entry(domain, + domain->dcname, + NT_STATUS_UNSUCCESSFUL); + return false; + } + return true; + +fail: + winbind_add_failed_connection_entry(domain, + saf_servername, + NT_STATUS_UNSUCCESSFUL); + return false; + +} + +/******************************************************************* + Find and make a connection to a DC in the given domain. + + @param[in] mem_ctx talloc memory context to allocate from + @param[in] domain domain to find a dc in + @param[out] fd fd of the open socket connected to the newly found dc + @return true when a DC connection is made, false otherwise +*******************************************************************/ + +static bool find_dc(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + uint32_t request_flags, + int *fd) +{ + struct dc_name_ip *dcs = NULL; + int num_dcs = 0; + + const char **dcnames = NULL; + size_t num_dcnames = 0; + + struct sockaddr_storage *addrs = NULL; + int num_addrs = 0; + + int i; + size_t fd_index; + + NTSTATUS status; + bool ok; + + *fd = -1; + + D_NOTICE("First try to connect to the closest DC (using server " + "affinity cache). If this fails, try to lookup the DC using " + "DNS afterwards.\n"); + ok = connect_preferred_dc(mem_ctx, domain, request_flags, fd); + if (ok) { + return true; + } + + if (domain->force_dc) { + return false; + } + + again: + D_DEBUG("Retrieving a list of IP addresses for DCs.\n"); + if (!get_dcs(mem_ctx, domain, &dcs, &num_dcs, request_flags) || (num_dcs == 0)) + return False; + + D_DEBUG("Retrieved IP addresses for %d DCs.\n", num_dcs); + for (i=0; i<num_dcs; i++) { + + if (!add_string_to_array(mem_ctx, dcs[i].name, + &dcnames, &num_dcnames)) { + return False; + } + if (!add_sockaddr_to_array(mem_ctx, &dcs[i].ss, TCP_SMB_PORT, + &addrs, &num_addrs)) { + return False; + } + } + + if ((num_dcnames == 0) || (num_dcnames != num_addrs)) + return False; + + if ((addrs == NULL) || (dcnames == NULL)) + return False; + + D_DEBUG("Trying to establish a connection to one of the %d DCs " + "(timeout of 10 sec for each DC).\n", + num_dcs); + status = smbsock_any_connect(addrs, dcnames, NULL, NULL, NULL, + num_addrs, 0, 10, fd, &fd_index, NULL); + if (!NT_STATUS_IS_OK(status)) { + for (i=0; i<num_dcs; i++) { + char ab[INET6_ADDRSTRLEN]; + print_sockaddr(ab, sizeof(ab), &dcs[i].ss); + DBG_DEBUG("smbsock_any_connect failed for " + "domain %s address %s. Error was %s\n", + domain->name, ab, nt_errstr(status)); + winbind_add_failed_connection_entry(domain, + dcs[i].name, NT_STATUS_UNSUCCESSFUL); + } + return False; + } + D_NOTICE("Successfully connected to DC '%s'.\n", dcs[fd_index].name); + + domain->dcaddr = addrs[fd_index]; + + if (*dcnames[fd_index] != '\0' && !is_ipaddress(dcnames[fd_index])) { + /* Ok, we've got a name for the DC */ + TALLOC_FREE(domain->dcname); + domain->dcname = talloc_strdup(domain, dcnames[fd_index]); + if (domain->dcname == NULL) { + return false; + } + return true; + } + + /* Try to figure out the name */ + TALLOC_FREE(domain->dcname); + ok = dcip_check_name(domain, + domain, + &domain->dcaddr, + &domain->dcname, + request_flags); + if (ok) { + return true; + } + + /* We can not continue without the DC's name */ + winbind_add_failed_connection_entry(domain, dcs[fd_index].name, + NT_STATUS_UNSUCCESSFUL); + + /* Throw away all arrays as we're doing this again. */ + TALLOC_FREE(dcs); + num_dcs = 0; + + TALLOC_FREE(dcnames); + num_dcnames = 0; + + TALLOC_FREE(addrs); + num_addrs = 0; + + if (*fd != -1) { + close(*fd); + *fd = -1; + } + + /* + * This should not be an infinite loop, since get_dcs() will not return + * the DC added to the negative connection cache in the above + * winbind_add_failed_connection_entry() call. + */ + goto again; +} + +static char *current_dc_key(TALLOC_CTX *mem_ctx, const char *domain_name) +{ + return talloc_asprintf_strupper_m(mem_ctx, "CURRENT_DCNAME/%s", + domain_name); +} + +static void store_current_dc_in_gencache(const char *domain_name, + const char *dc_name, + struct cli_state *cli) +{ + char addr[INET6_ADDRSTRLEN]; + char *key = NULL; + char *value = NULL; + + if (!cli_state_is_connected(cli)) { + return; + } + + print_sockaddr(addr, sizeof(addr), + smbXcli_conn_remote_sockaddr(cli->conn)); + + key = current_dc_key(talloc_tos(), domain_name); + if (key == NULL) { + goto done; + } + + value = talloc_asprintf(talloc_tos(), "%s %s", addr, dc_name); + if (value == NULL) { + goto done; + } + + gencache_set(key, value, 0x7fffffff); +done: + TALLOC_FREE(value); + TALLOC_FREE(key); +} + +bool fetch_current_dc_from_gencache(TALLOC_CTX *mem_ctx, + const char *domain_name, + char **p_dc_name, char **p_dc_ip) +{ + char *key, *p; + char *value = NULL; + bool ret = false; + char *dc_name = NULL; + char *dc_ip = NULL; + + key = current_dc_key(talloc_tos(), domain_name); + if (key == NULL) { + goto done; + } + if (!gencache_get(key, mem_ctx, &value, NULL)) { + goto done; + } + p = strchr(value, ' '); + if (p == NULL) { + goto done; + } + dc_ip = talloc_strndup(mem_ctx, value, p - value); + if (dc_ip == NULL) { + goto done; + } + dc_name = talloc_strdup(mem_ctx, p+1); + if (dc_name == NULL) { + goto done; + } + + if (p_dc_ip != NULL) { + *p_dc_ip = dc_ip; + dc_ip = NULL; + } + if (p_dc_name != NULL) { + *p_dc_name = dc_name; + dc_name = NULL; + } + ret = true; +done: + TALLOC_FREE(dc_name); + TALLOC_FREE(dc_ip); + TALLOC_FREE(key); + TALLOC_FREE(value); + return ret; +} + +NTSTATUS wb_open_internal_pipe(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *table, + struct rpc_pipe_client **ret_pipe) +{ + struct rpc_pipe_client *cli = NULL; + const struct auth_session_info *session_info = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + + session_info = get_session_info_system(); + SMB_ASSERT(session_info != NULL); + + status = rpc_pipe_open_local_np( + mem_ctx, table, NULL, NULL, NULL, NULL, session_info, &cli); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (ret_pipe) { + *ret_pipe = cli; + } + + return NT_STATUS_OK; +} + +static NTSTATUS cm_open_connection(struct winbindd_domain *domain, + struct winbindd_cm_conn *new_conn, + bool need_rw_dc) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + int retries; + uint32_t request_flags = need_rw_dc ? DS_WRITABLE_REQUIRED : 0; + int fd = -1; + bool retry = false; + bool seal_pipes = true; + + if ((mem_ctx = talloc_init("cm_open_connection")) == NULL) { + set_domain_offline(domain); + return NT_STATUS_NO_MEMORY; + } + + D_NOTICE("Creating connection to domain controller. This is a start of " + "a new connection or a DC failover. The failover only happens " + "if the domain has more than one DC. We will try to connect 3 " + "times at most.\n"); + for (retries = 0; retries < 3; retries++) { + bool found_dc; + + D_DEBUG("Attempt %d/3: DC '%s' of domain '%s'.\n", + retries, + domain->dcname ? domain->dcname : "", + domain->name); + + found_dc = find_dc(mem_ctx, domain, request_flags, &fd); + if (!found_dc) { + /* This is the one place where we will + set the global winbindd offline state + to true, if a "WINBINDD_OFFLINE" entry + is found in the winbindd cache. */ + set_global_winbindd_state_offline(); + result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + break; + } + + new_conn->cli = NULL; + + result = cm_prepare_connection(domain, fd, domain->dcname, + &new_conn->cli, &retry); + if (NT_STATUS_IS_OK(result)) { + break; + } + if (!retry) { + break; + } + } + + if (!NT_STATUS_IS_OK(result)) { + /* Ensure we setup the retry handler. */ + set_domain_offline(domain); + goto out; + } + + winbindd_set_locator_kdc_envs(domain); + + if (domain->online == False) { + /* We're changing state from offline to online. */ + set_global_winbindd_state_online(); + } + set_domain_online(domain); + + /* + * Much as I hate global state, this seems to be the point + * where we can be certain that we have a proper connection to + * a DC. wbinfo --dc-info needs that information, store it in + * gencache with a looong timeout. This will need revisiting + * once we start to connect to multiple DCs, wbcDcInfo is + * already prepared for that. + */ + store_current_dc_in_gencache(domain->name, domain->dcname, + new_conn->cli); + + seal_pipes = lp_winbind_sealed_pipes(); + seal_pipes = lp_parm_bool(-1, "winbind sealed pipes", + domain->name, + seal_pipes); + + if (seal_pipes) { + new_conn->auth_level = DCERPC_AUTH_LEVEL_PRIVACY; + } else { + new_conn->auth_level = DCERPC_AUTH_LEVEL_INTEGRITY; + } + +out: + talloc_destroy(mem_ctx); + return result; +} + +/* Close down all open pipes on a connection. */ + +void invalidate_cm_connection(struct winbindd_domain *domain) +{ + NTSTATUS result; + struct winbindd_cm_conn *conn = &domain->conn; + + domain->sequence_number = DOM_SEQUENCE_NONE; + domain->last_seq_check = 0; + domain->last_status = NT_STATUS_SERVER_DISABLED; + + /* We're closing down a possibly dead + connection. Don't have impossibly long (10s) timeouts. */ + + if (conn->cli) { + cli_set_timeout(conn->cli, 1000); /* 1 second. */ + } + + if (conn->samr_pipe != NULL) { + if (is_valid_policy_hnd(&conn->sam_connect_handle)) { + dcerpc_samr_Close(conn->samr_pipe->binding_handle, + talloc_tos(), + &conn->sam_connect_handle, + &result); + } + TALLOC_FREE(conn->samr_pipe); + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + + if (conn->lsa_pipe != NULL) { + if (is_valid_policy_hnd(&conn->lsa_policy)) { + dcerpc_lsa_Close(conn->lsa_pipe->binding_handle, + talloc_tos(), + &conn->lsa_policy, + &result); + } + TALLOC_FREE(conn->lsa_pipe); + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + + if (conn->lsa_pipe_tcp != NULL) { + if (is_valid_policy_hnd(&conn->lsa_policy)) { + dcerpc_lsa_Close(conn->lsa_pipe_tcp->binding_handle, + talloc_tos(), + &conn->lsa_policy, + &result); + } + TALLOC_FREE(conn->lsa_pipe_tcp); + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + + if (conn->netlogon_pipe != NULL) { + TALLOC_FREE(conn->netlogon_pipe); + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + + conn->auth_level = DCERPC_AUTH_LEVEL_PRIVACY; + TALLOC_FREE(conn->netlogon_creds_ctx); + + if (conn->cli) { + cli_shutdown(conn->cli); + } + + conn->cli = NULL; +} + +void close_conns_after_fork(void) +{ + struct winbindd_domain *domain; + struct winbindd_cli_state *cli_state; + + for (domain = domain_list(); domain; domain = domain->next) { + /* + * first close the low level SMB TCP connection + * so that we don't generate any SMBclose + * requests in invalidate_cm_connection() + */ + if (cli_state_is_connected(domain->conn.cli)) { + smbXcli_conn_disconnect(domain->conn.cli->conn, NT_STATUS_OK); + } + + invalidate_cm_connection(domain); + } + + for (cli_state = winbindd_client_list(); + cli_state != NULL; + cli_state = cli_state->next) { + if (cli_state->sock >= 0) { + close(cli_state->sock); + cli_state->sock = -1; + } + } +} + +static bool connection_ok(struct winbindd_domain *domain) +{ + bool ok; + + ok = cli_state_is_connected(domain->conn.cli); + if (!ok) { + DEBUG(3, ("connection_ok: Connection to %s for domain %s is not connected\n", + domain->dcname, domain->name)); + return False; + } + + if (!domain->online) { + DEBUG(3, ("connection_ok: Domain %s is offline\n", domain->name)); + return False; + } + + return True; +} + +/* Initialize a new connection up to the RPC BIND. + Bypass online status check so always does network calls. */ + +static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain, bool need_rw_dc) +{ + NTSTATUS result; + bool skip_connection = domain->internal; + if (need_rw_dc && domain->rodc) { + skip_connection = false; + } + + /* Internal connections never use the network. */ + if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + /* Still ask the internal LSA and SAMR server about the local domain */ + if (skip_connection || connection_ok(domain)) { + if (!domain->initialized) { + set_dc_type_and_flags(domain); + } + return NT_STATUS_OK; + } + + invalidate_cm_connection(domain); + + if (!domain->primary && !domain->initialized) { + /* + * Before we connect to a trust, work out if it is an + * AD domain by asking our own domain. + */ + set_dc_type_and_flags_trustinfo(domain); + } + + result = cm_open_connection(domain, &domain->conn, need_rw_dc); + + if (NT_STATUS_IS_OK(result) && !domain->initialized) { + set_dc_type_and_flags(domain); + } + + return result; +} + +NTSTATUS init_dc_connection(struct winbindd_domain *domain, bool need_rw_dc) +{ + if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + SMB_ASSERT(wb_child_domain() || idmap_child()); + + return init_dc_connection_network(domain, need_rw_dc); +} + +static NTSTATUS init_dc_connection_rpc(struct winbindd_domain *domain, bool need_rw_dc) +{ + NTSTATUS status; + + status = init_dc_connection(domain, need_rw_dc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!domain->internal && domain->conn.cli == NULL) { + /* happens for trusted domains without inbound trust */ + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + + return NT_STATUS_OK; +} + +/****************************************************************************** + Set the trust flags (direction and forest location) for a domain +******************************************************************************/ + +static bool set_dc_type_and_flags_trustinfo( struct winbindd_domain *domain ) +{ + struct winbindd_domain *our_domain; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + WERROR werr; + struct netr_DomainTrustList trusts; + int i; + uint32_t flags = (NETR_TRUST_FLAG_IN_FOREST | + NETR_TRUST_FLAG_OUTBOUND | + NETR_TRUST_FLAG_INBOUND); + struct rpc_pipe_client *cli; + TALLOC_CTX *mem_ctx = NULL; + struct dcerpc_binding_handle *b; + + if (IS_DC) { + /* + * On a DC we loaded all trusts + * from configuration and never learn + * new domains. + */ + return true; + } + + DEBUG(5, ("set_dc_type_and_flags_trustinfo: domain %s\n", domain->name )); + + /* Our primary domain doesn't need to worry about trust flags. + Force it to go through the network setup */ + if ( domain->primary ) { + return False; + } + + mem_ctx = talloc_stackframe(); + our_domain = find_our_domain(); + if (our_domain->internal) { + result = init_dc_connection(our_domain, false); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(3,("set_dc_type_and_flags_trustinfo: " + "Not able to make a connection to our domain: %s\n", + nt_errstr(result))); + TALLOC_FREE(mem_ctx); + return false; + } + } + + /* This won't work unless our domain is AD */ + if ( !our_domain->active_directory ) { + TALLOC_FREE(mem_ctx); + return False; + } + + if (our_domain->internal) { + result = wb_open_internal_pipe(mem_ctx, &ndr_table_netlogon, &cli); + } else if (!connection_ok(our_domain)) { + DEBUG(3,("set_dc_type_and_flags_trustinfo: " + "No connection to our domain!\n")); + TALLOC_FREE(mem_ctx); + return False; + } else { + result = cm_connect_netlogon(our_domain, &cli); + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(5, ("set_dc_type_and_flags_trustinfo: Could not open " + "a connection to %s for PIPE_NETLOGON (%s)\n", + domain->name, nt_errstr(result))); + TALLOC_FREE(mem_ctx); + return False; + } + b = cli->binding_handle; + + /* Use DsEnumerateDomainTrusts to get us the trust direction and type. */ + result = dcerpc_netr_DsrEnumerateDomainTrusts(b, mem_ctx, + cli->desthost, + flags, + &trusts, + &werr); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("set_dc_type_and_flags_trustinfo: " + "failed to query trusted domain list: %s\n", + nt_errstr(result))); + TALLOC_FREE(mem_ctx); + return false; + } + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,("set_dc_type_and_flags_trustinfo: " + "failed to query trusted domain list: %s\n", + win_errstr(werr))); + TALLOC_FREE(mem_ctx); + return false; + } + + /* Now find the domain name and get the flags */ + + for ( i=0; i<trusts.count; i++ ) { + if ( strequal( domain->name, trusts.array[i].netbios_name) ) { + domain->domain_flags = trusts.array[i].trust_flags; + domain->domain_type = trusts.array[i].trust_type; + domain->domain_trust_attribs = trusts.array[i].trust_attributes; + + if ( domain->domain_type == LSA_TRUST_TYPE_UPLEVEL ) + domain->active_directory = True; + + /* This flag is only set if the domain is *our* + primary domain and the primary domain is in + native mode */ + + domain->native_mode = (domain->domain_flags & NETR_TRUST_FLAG_NATIVE); + + DEBUG(5, ("set_dc_type_and_flags_trustinfo: domain %s is %sin " + "native mode.\n", domain->name, + domain->native_mode ? "" : "NOT ")); + + DEBUG(5,("set_dc_type_and_flags_trustinfo: domain %s is %s" + "running active directory.\n", domain->name, + domain->active_directory ? "" : "NOT ")); + + domain->can_do_ncacn_ip_tcp = domain->active_directory; + + domain->initialized = True; + + break; + } + } + + TALLOC_FREE(mem_ctx); + + return domain->initialized; +} + +/****************************************************************************** + We can 'sense' certain things about the DC by it's replies to certain + questions. + + This tells us if this particular remote server is Active Directory, and if it + is native mode. +******************************************************************************/ + +static void set_dc_type_and_flags_connect( struct winbindd_domain *domain ) +{ + NTSTATUS status, result; + NTSTATUS close_status = NT_STATUS_UNSUCCESSFUL; + WERROR werr; + TALLOC_CTX *mem_ctx = NULL; + struct rpc_pipe_client *cli = NULL; + struct policy_handle pol = { .handle_type = 0 }; + union dssetup_DsRoleInfo info; + union lsa_PolicyInformation *lsa_info = NULL; + union lsa_revision_info out_revision_info = { + .info1 = { + .revision = 0, + }, + }; + uint32_t out_version = 0; + + if (!domain->internal && !connection_ok(domain)) { + return; + } + + mem_ctx = talloc_init("set_dc_type_and_flags on domain %s\n", + domain->name); + if (!mem_ctx) { + DEBUG(1, ("set_dc_type_and_flags_connect: talloc_init() failed\n")); + return; + } + + DEBUG(5, ("set_dc_type_and_flags_connect: domain %s\n", domain->name )); + + if (domain->internal) { + status = wb_open_internal_pipe(mem_ctx, + &ndr_table_dssetup, + &cli); + } else { + status = cli_rpc_pipe_open_noauth(domain->conn.cli, + &ndr_table_dssetup, + &cli); + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to " + "PI_DSSETUP on domain %s: (%s)\n", + domain->name, nt_errstr(status))); + + /* if this is just a non-AD domain we need to continue + * identifying so that we can in the end return with + * domain->initialized = True - gd */ + + goto no_dssetup; + } + + status = dcerpc_dssetup_DsRoleGetPrimaryDomainInformation(cli->binding_handle, mem_ctx, + DS_ROLE_BASIC_INFORMATION, + &info, + &werr); + TALLOC_FREE(cli); + + if (NT_STATUS_IS_OK(status)) { + result = werror_to_ntstatus(werr); + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("set_dc_type_and_flags_connect: rpccli_ds_getprimarydominfo " + "on domain %s failed: (%s)\n", + domain->name, nt_errstr(status))); + + /* older samba3 DCs will return DCERPC_FAULT_OP_RNG_ERROR for + * every opcode on the DSSETUP pipe, continue with + * no_dssetup mode here as well to get domain->initialized + * set - gd */ + + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + goto no_dssetup; + } + + TALLOC_FREE(mem_ctx); + return; + } + + if ((info.basic.flags & DS_ROLE_PRIMARY_DS_RUNNING) && + !(info.basic.flags & DS_ROLE_PRIMARY_DS_MIXED_MODE)) { + domain->native_mode = True; + } else { + domain->native_mode = False; + } + +no_dssetup: + if (domain->internal) { + status = wb_open_internal_pipe(mem_ctx, + &ndr_table_lsarpc, + &cli); + } else { + status = cli_rpc_pipe_open_noauth(domain->conn.cli, + &ndr_table_lsarpc, &cli); + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to " + "PI_LSARPC on domain %s: (%s)\n", + domain->name, nt_errstr(status))); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + + status = dcerpc_lsa_open_policy_fallback(cli->binding_handle, + mem_ctx, + cli->srv_name_slash, + true, + SEC_FLAG_MAXIMUM_ALLOWED, + &out_version, + &out_revision_info, + &pol, + &result); + + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + /* This particular query is exactly what Win2k clients use + to determine that the DC is active directory */ + status = dcerpc_lsa_QueryInfoPolicy2(cli->binding_handle, mem_ctx, + &pol, + LSA_POLICY_INFO_DNS, + &lsa_info, + &result); + } + + /* + * If the status and result will not be OK we will fallback to + * OpenPolicy. + */ + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + domain->active_directory = True; + + if (lsa_info->dns.name.string) { + if (!strequal(domain->name, lsa_info->dns.name.string)) + { + DEBUG(1, ("set_dc_type_and_flags_connect: DC " + "for domain %s claimed it was a DC " + "for domain %s, refusing to " + "initialize\n", + domain->name, + lsa_info->dns.name.string)); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + talloc_free(domain->name); + domain->name = talloc_strdup(domain, + lsa_info->dns.name.string); + if (domain->name == NULL) { + goto done; + } + } + + if (lsa_info->dns.dns_domain.string) { + if (domain->alt_name != NULL && + !strequal(domain->alt_name, + lsa_info->dns.dns_domain.string)) + { + DEBUG(1, ("set_dc_type_and_flags_connect: DC " + "for domain %s (%s) claimed it was " + "a DC for domain %s, refusing to " + "initialize\n", + domain->alt_name, domain->name, + lsa_info->dns.dns_domain.string)); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + talloc_free(domain->alt_name); + domain->alt_name = + talloc_strdup(domain, + lsa_info->dns.dns_domain.string); + if (domain->alt_name == NULL) { + goto done; + } + } + + /* See if we can set some domain trust flags about + ourself */ + + if (lsa_info->dns.dns_forest.string) { + talloc_free(domain->forest_name); + domain->forest_name = + talloc_strdup(domain, + lsa_info->dns.dns_forest.string); + if (domain->forest_name == NULL) { + goto done; + } + + if (strequal(domain->forest_name, domain->alt_name)) { + domain->domain_flags |= NETR_TRUST_FLAG_TREEROOT; + } + } + + if (lsa_info->dns.sid) { + if (!is_null_sid(&domain->sid) && + !dom_sid_equal(&domain->sid, + lsa_info->dns.sid)) + { + struct dom_sid_buf buf1, buf2; + DEBUG(1, ("set_dc_type_and_flags_connect: DC " + "for domain %s (%s) claimed it was " + "a DC for domain %s, refusing to " + "initialize\n", + dom_sid_str_buf(&domain->sid, &buf1), + domain->name, + dom_sid_str_buf(lsa_info->dns.sid, + &buf2))); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + sid_copy(&domain->sid, lsa_info->dns.sid); + } + } else { + domain->active_directory = False; + + status = rpccli_lsa_open_policy(cli, mem_ctx, True, + SEC_FLAG_MAXIMUM_ALLOWED, + &pol); + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dcerpc_lsa_QueryInfoPolicy(cli->binding_handle, mem_ctx, + &pol, + LSA_POLICY_INFO_ACCOUNT_DOMAIN, + &lsa_info, + &result); + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + + if (lsa_info->account_domain.name.string) { + if (!strequal(domain->name, + lsa_info->account_domain.name.string)) + { + DEBUG(1, + ("set_dc_type_and_flags_connect: " + "DC for domain %s claimed it was" + " a DC for domain %s, refusing " + "to initialize\n", domain->name, + lsa_info-> + account_domain.name.string)); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + talloc_free(domain->name); + domain->name = + talloc_strdup(domain, + lsa_info->account_domain.name.string); + } + + if (lsa_info->account_domain.sid) { + if (!is_null_sid(&domain->sid) && + !dom_sid_equal(&domain->sid, + lsa_info->account_domain.sid)) + { + struct dom_sid_buf buf1, buf2; + DEBUG(1, + ("set_dc_type_and_flags_connect: " + "DC for domain %s (%s) claimed " + "it was a DC for domain %s, " + "refusing to initialize\n", + dom_sid_str_buf( + &domain->sid, &buf1), + domain->name, + dom_sid_str_buf( + lsa_info->account_domain.sid, + &buf2))); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + sid_copy(&domain->sid, lsa_info->account_domain.sid); + } + } + } +done: + if (is_valid_policy_hnd(&pol)) { + dcerpc_lsa_Close(cli->binding_handle, + mem_ctx, + &pol, + &close_status); + } + + DEBUG(5, ("set_dc_type_and_flags_connect: domain %s is %sin native mode.\n", + domain->name, domain->native_mode ? "" : "NOT ")); + + DEBUG(5,("set_dc_type_and_flags_connect: domain %s is %srunning active directory.\n", + domain->name, domain->active_directory ? "" : "NOT ")); + + domain->can_do_ncacn_ip_tcp = domain->active_directory; + + TALLOC_FREE(cli); + + TALLOC_FREE(mem_ctx); + + domain->initialized = True; +} + +/********************************************************************** + Set the domain_flags (trust attributes, domain operating modes, etc... +***********************************************************************/ + +static void set_dc_type_and_flags( struct winbindd_domain *domain ) +{ + if (IS_DC) { + /* + * On a DC we loaded all trusts + * from configuration and never learn + * new domains. + */ + return; + } + + /* we always have to contact our primary domain */ + + if ( domain->primary || domain->internal) { + DEBUG(10,("set_dc_type_and_flags: setting up flags for " + "primary or internal domain\n")); + set_dc_type_and_flags_connect( domain ); + return; + } + + /* Use our DC to get the information if possible */ + + if ( !set_dc_type_and_flags_trustinfo( domain ) ) { + /* Otherwise, fallback to contacting the + domain directly */ + set_dc_type_and_flags_connect( domain ); + } + + return; +} + + + +/********************************************************************** +***********************************************************************/ + +static NTSTATUS cm_get_schannel_creds(struct winbindd_domain *domain, + struct netlogon_creds_cli_context **ppdc) +{ + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + struct rpc_pipe_client *netlogon_pipe; + + *ppdc = NULL; + + if ((!IS_DC) && (!domain->primary)) { + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + + if (domain->conn.netlogon_creds_ctx != NULL) { + *ppdc = domain->conn.netlogon_creds_ctx; + return NT_STATUS_OK; + } + + result = cm_connect_netlogon_secure(domain, &netlogon_pipe, ppdc); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + return NT_STATUS_OK; +} + +NTSTATUS cm_connect_sam(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, + bool need_rw_dc, + struct rpc_pipe_client **cli, struct policy_handle *sam_handle) +{ + struct winbindd_cm_conn *conn; + NTSTATUS status, result; + struct netlogon_creds_cli_context *p_creds; + struct cli_credentials *creds = NULL; + bool retry = false; /* allow one retry attempt for expired session */ + const char *remote_name = NULL; + const struct sockaddr_storage *remote_sockaddr = NULL; + bool sealed_pipes = true; + bool strong_key = true; + + if (sid_check_is_our_sam(&domain->sid)) { + if (domain->rodc == false || need_rw_dc == false) { + return open_internal_samr_conn(mem_ctx, domain, cli, sam_handle); + } + } + + if (IS_AD_DC) { + /* + * In theory we should not use SAMR within + * winbindd at all, but that's a larger task to + * remove this and avoid breaking existing + * setups. + * + * At least as AD DC we have the restriction + * to avoid SAMR against trusted domains, + * as there're no existing setups. + */ + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + +retry: + status = init_dc_connection_rpc(domain, need_rw_dc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + conn = &domain->conn; + + if (rpccli_is_connected(conn->samr_pipe)) { + goto done; + } + + TALLOC_FREE(conn->samr_pipe); + + /* + * No SAMR pipe yet. Attempt to get an NTLMSSP SPNEGO authenticated + * sign and sealed pipe using the machine account password by + * preference. If we can't - try schannel, if that fails, try + * anonymous. + */ + + result = get_trust_credentials(domain, talloc_tos(), false, &creds); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10, ("cm_connect_sam: No user available for " + "domain %s, trying schannel\n", domain->name)); + goto schannel; + } + + if (cli_credentials_is_anonymous(creds)) { + goto anonymous; + } + + remote_name = smbXcli_conn_remote_name(conn->cli->conn); + remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn); + + /* + * We have an authenticated connection. Use a SPNEGO + * authenticated SAMR pipe with sign & seal. + */ + status = cli_rpc_pipe_open_with_creds(conn->cli, + &ndr_table_samr, + NCACN_NP, + DCERPC_AUTH_TYPE_SPNEGO, + conn->auth_level, + remote_name, + remote_sockaddr, + creds, + &conn->samr_pipe); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("cm_connect_sam: failed to connect to SAMR " + "pipe for domain %s using NTLMSSP " + "authenticated pipe: user %s. Error was " + "%s\n", domain->name, + cli_credentials_get_unparsed_name(creds, talloc_tos()), + nt_errstr(status))); + goto schannel; + } + + DEBUG(10,("cm_connect_sam: connected to SAMR pipe for " + "domain %s using NTLMSSP authenticated " + "pipe: user %s\n", domain->name, + cli_credentials_get_unparsed_name(creds, talloc_tos()))); + + status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx, + conn->samr_pipe->desthost, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->sam_connect_handle, + &result); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->samr_pipe); + retry = true; + goto retry; + } + + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + goto open_domain; + } + if (NT_STATUS_IS_OK(status)) { + status = result; + } + + DEBUG(10,("cm_connect_sam: ntlmssp-sealed dcerpc_samr_Connect2 " + "failed for domain %s, error was %s. Trying schannel\n", + domain->name, nt_errstr(status) )); + TALLOC_FREE(conn->samr_pipe); + + schannel: + + /* Fall back to schannel if it's a W2K pre-SP1 box. */ + + status = cm_get_schannel_creds(domain, &p_creds); + if (!NT_STATUS_IS_OK(status)) { + /* If this call fails - conn->cli can now be NULL ! */ + DEBUG(10, ("cm_connect_sam: Could not get schannel auth info " + "for domain %s (error %s), trying anon\n", + domain->name, + nt_errstr(status) )); + goto anonymous; + } + TALLOC_FREE(creds); + status = cli_rpc_pipe_open_schannel_with_creds( + conn->cli, &ndr_table_samr, NCACN_NP, p_creds, + remote_name, + remote_sockaddr, + &conn->samr_pipe); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("cm_connect_sam: failed to connect to SAMR pipe for " + "domain %s using schannel. Error was %s\n", + domain->name, nt_errstr(status) )); + goto anonymous; + } + DEBUG(10,("cm_connect_sam: connected to SAMR pipe for domain %s using " + "schannel.\n", domain->name )); + + status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx, + conn->samr_pipe->desthost, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->sam_connect_handle, + &result); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->samr_pipe); + retry = true; + goto retry; + } + + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + goto open_domain; + } + if (NT_STATUS_IS_OK(status)) { + status = result; + } + DEBUG(10,("cm_connect_sam: schannel-sealed dcerpc_samr_Connect2 failed " + "for domain %s, error was %s. Trying anonymous\n", + domain->name, nt_errstr(status) )); + TALLOC_FREE(conn->samr_pipe); + + anonymous: + + sealed_pipes = lp_winbind_sealed_pipes(); + sealed_pipes = lp_parm_bool(-1, "winbind sealed pipes", + domain->name, + sealed_pipes); + strong_key = lp_require_strong_key(); + strong_key = lp_parm_bool(-1, "require strong key", + domain->name, + strong_key); + + /* Finally fall back to anonymous. */ + if (sealed_pipes || strong_key) { + status = NT_STATUS_DOWNGRADE_DETECTED; + DEBUG(1, ("Unwilling to make SAMR connection to domain %s " + "without connection level security, " + "must set 'winbind sealed pipes:%s = false' and " + "'require strong key:%s = false' to proceed: %s\n", + domain->name, domain->name, domain->name, + nt_errstr(status))); + goto done; + } + status = cli_rpc_pipe_open_noauth(conn->cli, &ndr_table_samr, + &conn->samr_pipe); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx, + conn->samr_pipe->desthost, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->sam_connect_handle, + &result); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->samr_pipe); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("cm_connect_sam: rpccli_samr_Connect2 failed " + "for domain %s Error was %s\n", + domain->name, nt_errstr(status) )); + goto done; + } + if (!NT_STATUS_IS_OK(result)) { + status = result; + DEBUG(10,("cm_connect_sam: dcerpc_samr_Connect2 failed " + "for domain %s Error was %s\n", + domain->name, nt_errstr(result))); + goto done; + } + + open_domain: + status = dcerpc_samr_OpenDomain(conn->samr_pipe->binding_handle, + mem_ctx, + &conn->sam_connect_handle, + SEC_FLAG_MAXIMUM_ALLOWED, + &domain->sid, + &conn->sam_domain_handle, + &result); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = result; + done: + + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + /* + * if we got access denied, we might just have no access rights + * to talk to the remote samr server server (e.g. when we are a + * PDC and we are connecting a w2k8 pdc via an interdomain + * trust). In that case do not invalidate the whole connection + * stack + */ + TALLOC_FREE(conn->samr_pipe); + ZERO_STRUCT(conn->sam_domain_handle); + return status; + } else if (!NT_STATUS_IS_OK(status)) { + invalidate_cm_connection(domain); + return status; + } + + *cli = conn->samr_pipe; + *sam_handle = conn->sam_domain_handle; + return status; +} + +/********************************************************************** + open an schanneld ncacn_ip_tcp connection to LSA +***********************************************************************/ + +static NTSTATUS cm_connect_lsa_tcp(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli) +{ + struct winbindd_cm_conn *conn; + struct netlogon_creds_cli_context *p_creds = NULL; + NTSTATUS status; + const char *remote_name = NULL; + const struct sockaddr_storage *remote_sockaddr = NULL; + + DEBUG(10,("cm_connect_lsa_tcp\n")); + + status = init_dc_connection_rpc(domain, false); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + conn = &domain->conn; + + /* + * rpccli_is_connected handles more error cases + */ + if (rpccli_is_connected(conn->lsa_pipe_tcp) && + conn->lsa_pipe_tcp->transport->transport == NCACN_IP_TCP && + conn->lsa_pipe_tcp->auth->auth_level >= DCERPC_AUTH_LEVEL_INTEGRITY) { + goto done; + } + + TALLOC_FREE(conn->lsa_pipe_tcp); + + status = cm_get_schannel_creds(domain, &p_creds); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + remote_name = smbXcli_conn_remote_name(conn->cli->conn); + remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn); + + status = cli_rpc_pipe_open_schannel_with_creds( + conn->cli, + &ndr_table_lsarpc, + NCACN_IP_TCP, + p_creds, + remote_name, + remote_sockaddr, + &conn->lsa_pipe_tcp); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("cli_rpc_pipe_open_schannel_with_key failed: %s\n", + nt_errstr(status))); + goto done; + } + + done: + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(conn->lsa_pipe_tcp); + return status; + } + + *cli = conn->lsa_pipe_tcp; + + return status; +} + +NTSTATUS cm_connect_lsa(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli, struct policy_handle *lsa_policy) +{ + struct winbindd_cm_conn *conn; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + struct netlogon_creds_cli_context *p_creds; + struct cli_credentials *creds = NULL; + bool retry = false; /* allow one retry attempt for expired session */ + const char *remote_name = NULL; + const struct sockaddr_storage *remote_sockaddr = NULL; + bool sealed_pipes = true; + bool strong_key = true; + +retry: + result = init_dc_connection_rpc(domain, false); + if (!NT_STATUS_IS_OK(result)) + return result; + + conn = &domain->conn; + + if (rpccli_is_connected(conn->lsa_pipe)) { + goto done; + } + + TALLOC_FREE(conn->lsa_pipe); + + if (IS_DC) { + /* + * Make sure we only use schannel as AD DC. + */ + goto schannel; + } + + result = get_trust_credentials(domain, talloc_tos(), false, &creds); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10, ("cm_connect_lsa: No user available for " + "domain %s, trying schannel\n", domain->name)); + goto schannel; + } + + if (cli_credentials_is_anonymous(creds)) { + goto anonymous; + } + + remote_name = smbXcli_conn_remote_name(conn->cli->conn); + remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn); + + /* + * We have an authenticated connection. Use a SPNEGO + * authenticated LSA pipe with sign & seal. + */ + result = cli_rpc_pipe_open_with_creds + (conn->cli, &ndr_table_lsarpc, NCACN_NP, + DCERPC_AUTH_TYPE_SPNEGO, + conn->auth_level, + remote_name, + remote_sockaddr, + creds, + &conn->lsa_pipe); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for " + "domain %s using NTLMSSP authenticated pipe: user " + "%s. Error was %s. Trying schannel.\n", + domain->name, + cli_credentials_get_unparsed_name(creds, talloc_tos()), + nt_errstr(result))); + goto schannel; + } + + DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using " + "NTLMSSP authenticated pipe: user %s\n", + domain->name, cli_credentials_get_unparsed_name(creds, talloc_tos()))); + + result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->lsa_policy); + if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->lsa_pipe); + retry = true; + goto retry; + } + + if (NT_STATUS_IS_OK(result)) { + goto done; + } + + DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying " + "schannel\n")); + + TALLOC_FREE(conn->lsa_pipe); + + schannel: + + /* Fall back to schannel if it's a W2K pre-SP1 box. */ + + result = cm_get_schannel_creds(domain, &p_creds); + if (!NT_STATUS_IS_OK(result)) { + /* If this call fails - conn->cli can now be NULL ! */ + DEBUG(10, ("cm_connect_lsa: Could not get schannel auth info " + "for domain %s (error %s), trying anon\n", + domain->name, + nt_errstr(result) )); + goto anonymous; + } + + TALLOC_FREE(creds); + result = cli_rpc_pipe_open_schannel_with_creds( + conn->cli, &ndr_table_lsarpc, NCACN_NP, p_creds, + remote_name, + remote_sockaddr, + &conn->lsa_pipe); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for " + "domain %s using schannel. Error was %s\n", + domain->name, nt_errstr(result) )); + goto anonymous; + } + DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using " + "schannel.\n", domain->name )); + + result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->lsa_policy); + + if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->lsa_pipe); + retry = true; + goto retry; + } + + if (NT_STATUS_IS_OK(result)) { + goto done; + } + + if (IS_DC) { + /* + * Make sure we only use schannel as AD DC. + */ + goto done; + } + + DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying " + "anonymous\n")); + + TALLOC_FREE(conn->lsa_pipe); + + anonymous: + + if (IS_DC) { + /* + * Make sure we only use schannel as AD DC. + */ + goto done; + } + + sealed_pipes = lp_winbind_sealed_pipes(); + sealed_pipes = lp_parm_bool(-1, "winbind sealed pipes", + domain->name, + sealed_pipes); + strong_key = lp_require_strong_key(); + strong_key = lp_parm_bool(-1, "require strong key", + domain->name, + strong_key); + + /* Finally fall back to anonymous. */ + if (sealed_pipes || strong_key) { + result = NT_STATUS_DOWNGRADE_DETECTED; + DEBUG(1, ("Unwilling to make LSA connection to domain %s " + "without connection level security, " + "must set 'winbind sealed pipes:%s = false' and " + "'require strong key:%s = false' to proceed: %s\n", + domain->name, domain->name, domain->name, + nt_errstr(result))); + goto done; + } + + result = cli_rpc_pipe_open_noauth(conn->cli, + &ndr_table_lsarpc, + &conn->lsa_pipe); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->lsa_policy); + + if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->lsa_pipe); + retry = true; + goto retry; + } + + done: + if (!NT_STATUS_IS_OK(result)) { + invalidate_cm_connection(domain); + return result; + } + + *cli = conn->lsa_pipe; + *lsa_policy = conn->lsa_policy; + return result; +} + +/**************************************************************************** +Open a LSA connection to a DC, suitable for LSA lookup calls. +****************************************************************************/ + +NTSTATUS cm_connect_lsat(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli, + struct policy_handle *lsa_policy) +{ + NTSTATUS status; + + if (domain->can_do_ncacn_ip_tcp) { + status = cm_connect_lsa_tcp(domain, mem_ctx, cli); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR) || + NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) { + invalidate_cm_connection(domain); + status = cm_connect_lsa_tcp(domain, mem_ctx, cli); + } + if (NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * we tried twice to connect via ncan_ip_tcp and schannel and + * failed - maybe it is a trusted domain we can't connect to ? + * do not try tcp next time - gd + * + * This also prevents NETLOGON over TCP + */ + domain->can_do_ncacn_ip_tcp = false; + } + + status = cm_connect_lsa(domain, mem_ctx, cli, lsa_policy); + + return status; +} + +/**************************************************************************** + Open the netlogon pipe to this DC. +****************************************************************************/ + +static NTSTATUS cm_connect_netlogon_transport(struct winbindd_domain *domain, + enum dcerpc_transport_t transport, + struct rpc_pipe_client **cli) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + struct winbindd_cm_conn *conn; + NTSTATUS result; + enum netr_SchannelType sec_chan_type; + struct cli_credentials *creds = NULL; + + *cli = NULL; + + if (IS_DC) { + if (domain->secure_channel_type == SEC_CHAN_NULL) { + /* + * Make sure we don't even try to + * connect to a foreign domain + * without a direct outbound trust. + */ + return NT_STATUS_NO_TRUST_LSA_SECRET; + } + } + + result = init_dc_connection_rpc(domain, domain->rodc); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + conn = &domain->conn; + + if (rpccli_is_connected(conn->netlogon_pipe)) { + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; + } + + TALLOC_FREE(conn->netlogon_pipe); + TALLOC_FREE(conn->netlogon_creds_ctx); + + result = get_trust_credentials(domain, talloc_tos(), true, &creds); + if (!NT_STATUS_IS_OK(result)) { + DBG_DEBUG("No user available for domain %s when trying " + "schannel\n", domain->name); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + if (cli_credentials_is_anonymous(creds)) { + DBG_WARNING("get_trust_credential only gave anonymous for %s, " + "unable to make get NETLOGON credentials\n", + domain->name); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + sec_chan_type = cli_credentials_get_secure_channel_type(creds); + if (sec_chan_type == SEC_CHAN_NULL) { + const char *remote_name = + smbXcli_conn_remote_name(conn->cli->conn); + const struct sockaddr_storage *remote_sockaddr = + smbXcli_conn_remote_sockaddr(conn->cli->conn); + + if (transport == NCACN_IP_TCP) { + DBG_NOTICE("get_secure_channel_type gave SEC_CHAN_NULL " + "for %s, deny NCACN_IP_TCP and let the " + "caller fallback to NCACN_NP.\n", + domain->name); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + DBG_NOTICE("get_secure_channel_type gave SEC_CHAN_NULL for %s, " + "fallback to noauth on NCACN_NP.\n", + domain->name); + + result = cli_rpc_pipe_open_noauth_transport( + conn->cli, + transport, + &ndr_table_netlogon, + remote_name, + remote_sockaddr, + &conn->netlogon_pipe); + if (!NT_STATUS_IS_OK(result)) { + invalidate_cm_connection(domain); + return result; + } + + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; + } + + result = rpccli_create_netlogon_creds_ctx(creds, + domain->dcname, + msg_ctx, + domain, + &conn->netlogon_creds_ctx); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("rpccli_create_netlogon_creds failed for %s, " + "unable to create NETLOGON credentials: %s\n", + domain->name, nt_errstr(result))); + return result; + } + + result = rpccli_connect_netlogon( + conn->cli, transport, + conn->netlogon_creds_ctx, conn->netlogon_force_reauth, creds, + &conn->netlogon_pipe); + conn->netlogon_force_reauth = false; + if (!NT_STATUS_IS_OK(result)) { + DBG_DEBUG("rpccli_connect_netlogon failed: %s\n", + nt_errstr(result)); + return result; + } + + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; +} + +/**************************************************************************** +Open a NETLOGON connection to a DC, suitable for SamLogon calls. +****************************************************************************/ + +NTSTATUS cm_connect_netlogon(struct winbindd_domain *domain, + struct rpc_pipe_client **cli) +{ + NTSTATUS status; + + status = init_dc_connection_rpc(domain, domain->rodc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (domain->active_directory && domain->can_do_ncacn_ip_tcp) { + status = cm_connect_netlogon_transport(domain, NCACN_IP_TCP, cli); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR) || + NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) { + invalidate_cm_connection(domain); + status = cm_connect_netlogon_transport(domain, NCACN_IP_TCP, cli); + } + if (NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * we tried twice to connect via ncan_ip_tcp and schannel and + * failed - maybe it is a trusted domain we can't connect to ? + * do not try tcp next time - gd + * + * This also prevents LSA over TCP + */ + domain->can_do_ncacn_ip_tcp = false; + } + + status = cm_connect_netlogon_transport(domain, NCACN_NP, cli); + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) { + /* + * SMB2 session expired, needs reauthentication. Drop + * connection and retry. + */ + invalidate_cm_connection(domain); + status = cm_connect_netlogon_transport(domain, NCACN_NP, cli); + } + + return status; +} + +NTSTATUS cm_connect_netlogon_secure(struct winbindd_domain *domain, + struct rpc_pipe_client **cli, + struct netlogon_creds_cli_context **ppdc) +{ + NTSTATUS status; + + if (domain->secure_channel_type == SEC_CHAN_NULL) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + status = cm_connect_netlogon(domain, cli); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (domain->conn.netlogon_creds_ctx == NULL) { + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + + *ppdc = domain->conn.netlogon_creds_ctx; + return NT_STATUS_OK; +} + +void winbind_msg_ip_dropped(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbindd_domain *domain; + char *freeit = NULL; + char *addr; + + if ((data == NULL) + || (data->data == NULL) + || (data->length == 0) + || (data->data[data->length-1] != '\0')) { + DEBUG(1, ("invalid msg_ip_dropped message: not a valid " + "string\n")); + return; + } + + addr = (char *)data->data; + DEBUG(10, ("IP %s dropped\n", addr)); + + if (!is_ipaddress(addr)) { + char *slash; + /* + * Some code sends us ip addresses with the /netmask + * suffix + */ + slash = strchr(addr, '/'); + if (slash == NULL) { + DEBUG(1, ("invalid msg_ip_dropped message: %s\n", + addr)); + return; + } + freeit = talloc_strndup(talloc_tos(), addr, slash-addr); + if (freeit == NULL) { + DEBUG(1, ("talloc failed\n")); + return; + } + addr = freeit; + DEBUG(10, ("Stripped /netmask to IP %s\n", addr)); + } + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + char sockaddr[INET6_ADDRSTRLEN]; + + if (!cli_state_is_connected(domain->conn.cli)) { + continue; + } + + print_sockaddr(sockaddr, sizeof(sockaddr), + smbXcli_conn_local_sockaddr(domain->conn.cli->conn)); + + if (strequal(sockaddr, addr)) { + smbXcli_conn_disconnect(domain->conn.cli->conn, NT_STATUS_OK); + } + } + TALLOC_FREE(freeit); +} + +void winbind_msg_disconnect_dc(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbindd_domain *domain; + + for (domain = domain_list(); domain; domain = domain->next) { + if (domain->internal) { + continue; + } + invalidate_cm_connection(domain); + } +} |