From 28aa4bff8d6be031c6089fe5c7ab010f1cc48340 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 14 Sep 2007 12:03:58 +0000 Subject: r25154: move winbindd code into winbindd/ metze (This used to be commit 3ac7566ae14c48ff9b0f6b232e0ec4b2f73df558) --- source3/winbindd/winbindd_cm.c | 2271 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2271 insertions(+) create mode 100644 source3/winbindd/winbindd_cm.c (limited to 'source3/winbindd/winbindd_cm.c') diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c new file mode 100644 index 0000000000..9ffb3dfb23 --- /dev/null +++ b/source3/winbindd/winbindd_cm.c @@ -0,0 +1,2271 @@ +/* + 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 . +*/ + +/* + 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +struct dc_name_ip { + fstring name; + struct in_addr ip; +}; + +extern struct winbindd_methods reconnect_methods; +extern BOOL override_logfile; + +static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain); +static void set_dc_type_and_flags( struct winbindd_domain *domain ); +static BOOL get_dcs(TALLOC_CTX *mem_ctx, const struct winbindd_domain *domain, + struct dc_name_ip **dcs, int *num_dcs); + +/**************************************************************** + Child failed to find DC's. Reschedule check. +****************************************************************/ + +static void msg_failed_to_go_online(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbindd_domain *domain; + const char *domainname = (const char *)data->data; + + if (data->data == NULL || data->length == 0) { + return; + } + + DEBUG(5,("msg_fail_to_go_online: received for domain %s.\n", domainname)); + + for (domain = domain_list(); domain; domain = domain->next) { + if (domain->internal) { + continue; + } + + if (strequal(domain->name, domainname)) { + if (domain->online) { + /* We're already online, ignore. */ + DEBUG(5,("msg_fail_to_go_online: domain %s " + "already online.\n", domainname)); + continue; + } + + /* Reschedule the online check. */ + set_domain_offline(domain); + break; + } + } +} + +/**************************************************************** + Actually cause a reconnect from a message. +****************************************************************/ + +static void msg_try_to_go_online(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbindd_domain *domain; + const char *domainname = (const char *)data->data; + + if (data->data == NULL || data->length == 0) { + return; + } + + DEBUG(5,("msg_try_to_go_online: received for domain %s.\n", domainname)); + + for (domain = domain_list(); domain; domain = domain->next) { + if (domain->internal) { + continue; + } + + if (strequal(domain->name, domainname)) { + + if (domain->online) { + /* We're already online, ignore. */ + DEBUG(5,("msg_try_to_go_online: domain %s " + "already online.\n", domainname)); + continue; + } + + /* This call takes care of setting the online + flag to true if we connected, or re-adding + the offline handler if false. Bypasses online + check so always does network calls. */ + + init_dc_connection_network(domain); + break; + } + } +} + +/**************************************************************** + Fork a child to try and contact a DC. Do this as contacting a + DC requires blocking lookups and we don't want to block our + parent. +****************************************************************/ + +static BOOL fork_child_dc_connect(struct winbindd_domain *domain) +{ + struct dc_name_ip *dcs = NULL; + int num_dcs = 0; + TALLOC_CTX *mem_ctx = NULL; + pid_t child_pid; + pid_t parent_pid = sys_getpid(); + + /* Stop zombies */ + CatchChild(); + + child_pid = sys_fork(); + + if (child_pid == -1) { + DEBUG(0, ("fork_child_dc_connect: Could not fork: %s\n", strerror(errno))); + return False; + } + + if (child_pid != 0) { + /* Parent */ + messaging_register(winbind_messaging_context(), NULL, + MSG_WINBIND_TRY_TO_GO_ONLINE, + msg_try_to_go_online); + messaging_register(winbind_messaging_context(), NULL, + MSG_WINBIND_FAILED_TO_GO_ONLINE, + msg_failed_to_go_online); + return True; + } + + /* Child. */ + + /* Leave messages blocked - we will never process one. */ + + /* tdb needs special fork handling */ + if (tdb_reopen_all(1) == -1) { + DEBUG(0,("tdb_reopen_all failed.\n")); + _exit(0); + } + + close_conns_after_fork(); + + if (!override_logfile) { + pstring logfile; + pstr_sprintf(logfile, "%s/log.winbindd-dc-connect", dyn_LOGFILEBASE); + lp_set_logfile(logfile); + reopen_logs(); + } + + mem_ctx = talloc_init("fork_child_dc_connect"); + if (!mem_ctx) { + DEBUG(0,("talloc_init failed.\n")); + _exit(0); + } + + if ((!get_dcs(mem_ctx, domain, &dcs, &num_dcs)) || (num_dcs == 0)) { + /* Still offline ? Can't find DC's. */ + messaging_send_buf(winbind_messaging_context(), + pid_to_procid(parent_pid), + MSG_WINBIND_FAILED_TO_GO_ONLINE, + (uint8 *)domain->name, + strlen(domain->name)+1); + _exit(0); + } + + /* We got a DC. Send a message to our parent to get it to + try and do the same. */ + + messaging_send_buf(winbind_messaging_context(), + pid_to_procid(parent_pid), + MSG_WINBIND_TRY_TO_GO_ONLINE, + (uint8 *)domain->name, + strlen(domain->name)+1); + _exit(0); +} + +/**************************************************************** + Handler triggered if we're offline to try and detect a DC. +****************************************************************/ + +static void check_domain_online_handler(struct event_context *ctx, + struct timed_event *te, + const struct timeval *now, + void *private_data) +{ + struct winbindd_domain *domain = + (struct winbindd_domain *)private_data; + + DEBUG(10,("check_domain_online_handler: called for domain " + "%s (online = %s)\n", domain->name, + domain->online ? "True" : "False" )); + + TALLOC_FREE(domain->check_online_event); + + /* Are we still in "startup" mode ? */ + + if (domain->startup && (now->tv_sec > domain->startup_time + 30)) { + /* No longer in "startup" mode. */ + DEBUG(10,("check_domain_online_handler: domain %s no longer in 'startup' mode.\n", + domain->name )); + domain->startup = False; + } + + /* We've been told to stay offline, so stay + that way. */ + + if (get_global_winbindd_state_offline()) { + DEBUG(10,("check_domain_online_handler: domain %s remaining globally offline\n", + domain->name )); + return; + } + + /* Fork a child to test if it can contact a DC. + If it can then send ourselves a message to + cause a reconnect. */ + + fork_child_dc_connect(domain); +} + +/**************************************************************** + If we're still offline setup the timeout check. +****************************************************************/ + +static void calc_new_online_timeout_check(struct winbindd_domain *domain) +{ + int wbc = lp_winbind_cache_time(); + + if (domain->startup) { + domain->check_online_timeout = 10; + } else if (domain->check_online_timeout < wbc) { + domain->check_online_timeout = wbc; + } +} + +/**************************************************************** + 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) +{ + DEBUG(10,("set_domain_offline: called for domain %s\n", + domain->name )); + + TALLOC_FREE(domain->check_online_event); + + 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; + + /* We only add the timeout handler that checks and + allows us to go back online when we've not + been told to remain offline. */ + + if (get_global_winbindd_state_offline()) { + DEBUG(10,("set_domain_offline: domain %s remaining globally offline\n", + domain->name )); + return; + } + + /* If we're in statup mode, check again in 10 seconds, not in + lp_winbind_cache_time() seconds (which is 5 mins by default). */ + + calc_new_online_timeout_check(domain); + + domain->check_online_event = event_add_timed(winbind_event_context(), + NULL, + timeval_current_ofs(domain->check_online_timeout,0), + "check_domain_online_handler", + check_domain_online_handler, + domain); + + /* The above *has* to succeed for winbindd to work. */ + if (!domain->check_online_event) { + smb_panic("set_domain_offline: failed to add online handler"); + } + + DEBUG(10,("set_domain_offline: added event handler for domain %s\n", + domain->name )); + + /* Send an offline message to the idmap child when our + primary domain goes offline */ + + if ( domain->primary ) { + struct winbindd_child *idmap = idmap_child(); + + if ( idmap->pid != 0 ) { + messaging_send_buf(winbind_messaging_context(), + pid_to_procid(idmap->pid), + MSG_WINBIND_OFFLINE, + (uint8 *)domain->name, + strlen(domain->name)+1); + } + } + + return; +} + +/**************************************************************** + Set domain online - if allowed. +****************************************************************/ + +static void set_domain_online(struct winbindd_domain *domain) +{ + struct timeval now; + + 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. */ + GetTimeOfDay(&now); + set_event_dispatch_time(winbind_event_context(), + "krb5_ticket_gain_handler", 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; + } + } + + /* Ensure we have no online timeout checks. */ + domain->check_online_timeout = 0; + TALLOC_FREE(domain->check_online_event); + + /* Ensure we ignore any pending child messages. */ + messaging_deregister(winbind_messaging_context(), + MSG_WINBIND_TRY_TO_GO_ONLINE, NULL); + messaging_deregister(winbind_messaging_context(), + MSG_WINBIND_FAILED_TO_GO_ONLINE, NULL); + + domain->online = True; + + /* Send an online message to the idmap child when our + primary domain comes online */ + + if ( domain->primary ) { + struct winbindd_child *idmap = idmap_child(); + + if ( idmap->pid != 0 ) { + messaging_send_buf(winbind_messaging_context(), + pid_to_procid(idmap->pid), + MSG_WINBIND_ONLINE, + (uint8 *)domain->name, + strlen(domain->name)+1); + } + } + + return; +} + +/**************************************************************** + Requested to set a domain online. +****************************************************************/ + +void set_domain_online_request(struct winbindd_domain *domain) +{ + struct timeval tev; + + 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; + } + + /* We've been told it's safe to go online and + try and connect to a DC. But I don't believe it + because network manager seems to lie. + Wait at least 5 seconds. Heuristics suck... */ + + if (!domain->check_online_event) { + /* If we've come from being globally offline we + don't have a check online event handler set. + We need to add one now we're trying to go + back online. */ + + DEBUG(10,("set_domain_online_request: domain %s was globally offline.\n", + domain->name )); + + domain->check_online_event = event_add_timed(winbind_event_context(), + NULL, + timeval_current_ofs(5, 0), + "check_domain_online_handler", + check_domain_online_handler, + domain); + + /* The above *has* to succeed for winbindd to work. */ + if (!domain->check_online_event) { + smb_panic("set_domain_online_request: failed to add online handler"); + } + } + + GetTimeOfDay(&tev); + + /* Go into "startup" mode again. */ + domain->startup_time = tev.tv_sec; + domain->startup = True; + + tev.tv_sec += 5; + + set_event_dispatch_time(winbind_event_context(), "check_domain_online_handler", tev); +} + +/**************************************************************** + Add -ve connection cache entries for domain and realm. +****************************************************************/ + +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) { + 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 BOOL get_dc_name_via_netlogon(const struct winbindd_domain *domain, + fstring dcname, struct in_addr *dc_ip) +{ + 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; + fstring tmp; + char *p; + + /* 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; + } + + /* This call can take a long time - allow the server to time out. + 35 seconds should do it. */ + + orig_timeout = cli_set_timeout(netlogon_pipe->cli, 35000); + + werr = rpccli_netlogon_getanydcname(netlogon_pipe, mem_ctx, our_domain->dcname, + domain->name, tmp); + + /* And restore our original timeout. */ + cli_set_timeout(netlogon_pipe->cli, orig_timeout); + + talloc_destroy(mem_ctx); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(10, ("rpccli_netlogon_getanydcname failed: %s\n", + dos_errstr(werr))); + return False; + } + + /* cli_netlogon_getanydcname gives us a name with \\ */ + p = tmp; + if (*p == '\\') { + p+=1; + } + if (*p == '\\') { + p+=1; + } + + fstrcpy(dcname, p); + + DEBUG(10, ("rpccli_netlogon_getanydcname returned %s\n", dcname)); + + if (!resolve_name(dcname, dc_ip, 0x20)) { + return False; + } + + return True; +} + +/************************************************************************ + Given a fd with a just-connected TCP connection to a DC, open a connection + to the pipe. +************************************************************************/ + +static NTSTATUS cm_prepare_connection(const struct winbindd_domain *domain, + const int sockfd, + const char *controller, + struct cli_state **cli, + BOOL *retry) +{ + char *machine_password, *machine_krb5_principal, *machine_account; + char *ipc_username, *ipc_domain, *ipc_password; + + BOOL got_mutex; + + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + + struct sockaddr peeraddr; + socklen_t peeraddr_len; + + struct sockaddr_in *peeraddr_in = (struct sockaddr_in *)&peeraddr; + + DEBUG(10,("cm_prepare_connection: connecting to DC %s for domain %s\n", + controller, domain->name )); + + machine_password = secrets_fetch_machine_password(lp_workgroup(), NULL, + NULL); + + if (asprintf(&machine_account, "%s$", global_myname()) == -1) { + SAFE_FREE(machine_password); + return NT_STATUS_NO_MEMORY; + } + + if (asprintf(&machine_krb5_principal, "%s$@%s", global_myname(), + lp_realm()) == -1) { + SAFE_FREE(machine_account); + SAFE_FREE(machine_password); + return NT_STATUS_NO_MEMORY; + } + + cm_get_ipc_userpass(&ipc_username, &ipc_domain, &ipc_password); + + *retry = True; + + got_mutex = secrets_named_mutex(controller, + WINBIND_SERVER_MUTEX_WAIT_TIME); + + if (!got_mutex) { + DEBUG(0,("cm_prepare_connection: mutex grab failed for %s\n", + controller)); + result = NT_STATUS_POSSIBLE_DEADLOCK; + goto done; + } + + if ((*cli = cli_initialise()) == NULL) { + DEBUG(1, ("Could not cli_initialize\n")); + result = NT_STATUS_NO_MEMORY; + goto done; + } + + (*cli)->timeout = 10000; /* 10 seconds */ + (*cli)->fd = sockfd; + fstrcpy((*cli)->desthost, controller); + (*cli)->use_kerberos = True; + + peeraddr_len = sizeof(peeraddr); + + if ((getpeername((*cli)->fd, &peeraddr, &peeraddr_len) != 0) || + (peeraddr_len != sizeof(struct sockaddr_in)) || + (peeraddr_in->sin_family != PF_INET)) + { + DEBUG(0,("cm_prepare_connection: %s\n", strerror(errno))); + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + if (ntohs(peeraddr_in->sin_port) == 139) { + struct nmb_name calling; + struct nmb_name called; + + make_nmb_name(&calling, global_myname(), 0x0); + make_nmb_name(&called, "*SMBSERVER", 0x20); + + if (!cli_session_request(*cli, &calling, &called)) { + DEBUG(8, ("cli_session_request failed for %s\n", + controller)); + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + cli_setup_signing_state(*cli, Undefined); + + if (!cli_negprot(*cli)) { + DEBUG(1, ("cli_negprot failed\n")); + result = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + if ((*cli)->protocol >= PROTOCOL_NT1 && (*cli)->capabilities & CAP_EXTENDED_SECURITY) { + ADS_STATUS ads_status; + + if (lp_security() == SEC_ADS) { + + /* Try a krb5 session */ + + (*cli)->use_kerberos = True; + DEBUG(5, ("connecting to %s from %s with kerberos principal " + "[%s]\n", controller, global_myname(), + machine_krb5_principal)); + + winbindd_set_locator_kdc_envs(domain); + + ads_status = cli_session_setup_spnego(*cli, + machine_krb5_principal, + machine_password, + lp_workgroup()); + + if (!ADS_ERR_OK(ads_status)) { + DEBUG(4,("failed kerberos session setup with %s\n", + ads_errstr(ads_status))); + } + + result = ads_ntstatus(ads_status); + if (NT_STATUS_IS_OK(result)) { + /* Ensure creds are stored for NTLMSSP authenticated pipe access. */ + cli_init_creds(*cli, machine_account, lp_workgroup(), machine_password); + goto session_setup_done; + } + } + + /* Fall back to non-kerberos session setup using NTLMSSP SPNEGO with the machine account. */ + (*cli)->use_kerberos = False; + + DEBUG(5, ("connecting to %s from %s with username " + "[%s]\\[%s]\n", controller, global_myname(), + lp_workgroup(), machine_account)); + + ads_status = cli_session_setup_spnego(*cli, + machine_account, + machine_password, + lp_workgroup()); + if (!ADS_ERR_OK(ads_status)) { + DEBUG(4, ("authenticated session setup failed with %s\n", + ads_errstr(ads_status))); + } + + result = ads_ntstatus(ads_status); + if (NT_STATUS_IS_OK(result)) { + /* Ensure creds are stored for NTLMSSP authenticated pipe access. */ + cli_init_creds(*cli, machine_account, lp_workgroup(), machine_password); + goto session_setup_done; + } + } + + /* Fall back to non-kerberos session setup */ + + (*cli)->use_kerberos = False; + + if ((((*cli)->sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) != 0) && + (strlen(ipc_username) > 0)) { + + /* Only try authenticated if we have a username */ + + DEBUG(5, ("connecting to %s from %s with username " + "[%s]\\[%s]\n", controller, global_myname(), + ipc_domain, ipc_username)); + + if (NT_STATUS_IS_OK(cli_session_setup( + *cli, ipc_username, + ipc_password, strlen(ipc_password)+1, + ipc_password, strlen(ipc_password)+1, + ipc_domain))) { + /* Successful logon with given username. */ + cli_init_creds(*cli, ipc_username, ipc_domain, ipc_password); + goto session_setup_done; + } else { + DEBUG(4, ("authenticated session setup with user %s\\%s failed.\n", + ipc_domain, ipc_username )); + } + } + + /* Fall back to anonymous connection, this might fail later */ + + if (NT_STATUS_IS_OK(cli_session_setup(*cli, "", NULL, 0, + NULL, 0, ""))) { + DEBUG(5, ("Connected anonymously\n")); + cli_init_creds(*cli, "", "", ""); + goto session_setup_done; + } + + result = cli_nt_error(*cli); + + if (NT_STATUS_IS_OK(result)) + result = NT_STATUS_UNSUCCESSFUL; + + /* We can't session setup */ + + goto done; + + session_setup_done: + + /* cache the server name for later connections */ + + saf_store( domain->name, (*cli)->desthost ); + if (domain->alt_name && (*cli)->use_kerberos) { + saf_store( domain->alt_name, (*cli)->desthost ); + } + + winbindd_set_locator_kdc_envs(domain); + + if (!cli_send_tconX(*cli, "IPC$", "IPC", "", 0)) { + + result = cli_nt_error(*cli); + + DEBUG(1,("failed tcon_X with %s\n", nt_errstr(result))); + + if (NT_STATUS_IS_OK(result)) + result = NT_STATUS_UNSUCCESSFUL; + + goto done; + } + + secrets_named_mutex_release(controller); + got_mutex = False; + *retry = False; + + /* set the domain if empty; needed for schannel connections */ + if ( !*(*cli)->domain ) { + fstrcpy( (*cli)->domain, domain->name ); + } + + result = NT_STATUS_OK; + + done: + if (got_mutex) { + secrets_named_mutex_release(controller); + } + + SAFE_FREE(machine_account); + SAFE_FREE(machine_password); + SAFE_FREE(machine_krb5_principal); + SAFE_FREE(ipc_username); + SAFE_FREE(ipc_domain); + SAFE_FREE(ipc_password); + + if (!NT_STATUS_IS_OK(result)) { + winbind_add_failed_connection_entry(domain, controller, result); + if ((*cli) != NULL) { + cli_shutdown(*cli); + *cli = NULL; + } + } + + return result; +} + +static BOOL add_one_dc_unique(TALLOC_CTX *mem_ctx, const char *domain_name, + const char *dcname, struct in_addr ip, + struct dc_name_ip **dcs, int *num) +{ + 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; + } + + *dcs = TALLOC_REALLOC_ARRAY(mem_ctx, *dcs, struct dc_name_ip, (*num)+1); + + if (*dcs == NULL) + return False; + + fstrcpy((*dcs)[*num].name, dcname); + (*dcs)[*num].ip = ip; + *num += 1; + return True; +} + +static BOOL add_sockaddr_to_array(TALLOC_CTX *mem_ctx, + struct in_addr ip, uint16 port, + struct sockaddr_in **addrs, int *num) +{ + *addrs = TALLOC_REALLOC_ARRAY(mem_ctx, *addrs, struct sockaddr_in, (*num)+1); + + if (*addrs == NULL) { + *num = 0; + return False; + } + + (*addrs)[*num].sin_family = PF_INET; + putip((char *)&((*addrs)[*num].sin_addr), (char *)&ip); + (*addrs)[*num].sin_port = htons(port); + + *num += 1; + return True; +} + +static void mailslot_name(struct in_addr dc_ip, fstring name) +{ + fstr_sprintf(name, "\\MAILSLOT\\NET\\GETDC%X", dc_ip.s_addr); +} + +static BOOL send_getdc_request(struct in_addr dc_ip, + const char *domain_name, + const DOM_SID *sid) +{ + pstring outbuf; + char *p; + fstring my_acct_name; + fstring my_mailslot; + + mailslot_name(dc_ip, my_mailslot); + + memset(outbuf, '\0', sizeof(outbuf)); + + p = outbuf; + + SCVAL(p, 0, SAMLOGON); + p++; + + SCVAL(p, 0, 0); /* Count pointer ... */ + p++; + + SIVAL(p, 0, 0); /* The sender's token ... */ + p += 2; + + p += dos_PutUniCode(p, global_myname(), sizeof(pstring), True); + fstr_sprintf(my_acct_name, "%s$", global_myname()); + p += dos_PutUniCode(p, my_acct_name, sizeof(pstring), True); + + memcpy(p, my_mailslot, strlen(my_mailslot)+1); + p += strlen(my_mailslot)+1; + + SIVAL(p, 0, 0x80); + p+=4; + + SIVAL(p, 0, sid_size(sid)); + p+=4; + + p = ALIGN4(p, outbuf); + + sid_linearize(p, sid_size(sid), sid); + p += sid_size(sid); + + SIVAL(p, 0, 1); + SSVAL(p, 4, 0xffff); + SSVAL(p, 6, 0xffff); + p+=8; + + return cli_send_mailslot(winbind_messaging_context(), + False, "\\MAILSLOT\\NET\\NTLOGON", 0, + outbuf, PTR_DIFF(p, outbuf), + global_myname(), 0, domain_name, 0x1c, + dc_ip); +} + +static BOOL receive_getdc_response(struct in_addr dc_ip, + const char *domain_name, + fstring dc_name) +{ + struct packet_struct *packet; + fstring my_mailslot; + char *buf, *p; + fstring dcname, user, domain; + int len; + + mailslot_name(dc_ip, my_mailslot); + + packet = receive_unexpected(DGRAM_PACKET, 0, my_mailslot); + + if (packet == NULL) { + DEBUG(5, ("Did not receive packet for %s\n", my_mailslot)); + return False; + } + + DEBUG(5, ("Received packet for %s\n", my_mailslot)); + + buf = packet->packet.dgram.data; + len = packet->packet.dgram.datasize; + + if (len < 70) { + /* 70 is a completely arbitrary value to make sure + the SVAL below does not read uninitialized memory */ + DEBUG(3, ("GetDC got short response\n")); + return False; + } + + /* This should be (buf-4)+SVAL(buf-4, smb_vwv12)... */ + p = buf+SVAL(buf, smb_vwv10); + + if (CVAL(p,0) != SAMLOGON_R) { + DEBUG(8, ("GetDC got invalid response type %d\n", CVAL(p, 0))); + return False; + } + + p+=2; + pull_ucs2(buf, dcname, p, sizeof(dcname), PTR_DIFF(buf+len, p), + STR_TERMINATE|STR_NOALIGN); + p = skip_unibuf(p, PTR_DIFF(buf+len, p)); + pull_ucs2(buf, user, p, sizeof(dcname), PTR_DIFF(buf+len, p), + STR_TERMINATE|STR_NOALIGN); + p = skip_unibuf(p, PTR_DIFF(buf+len, p)); + pull_ucs2(buf, domain, p, sizeof(dcname), PTR_DIFF(buf+len, p), + STR_TERMINATE|STR_NOALIGN); + p = skip_unibuf(p, PTR_DIFF(buf+len, p)); + + if (!strequal(domain, domain_name)) { + DEBUG(3, ("GetDC: Expected domain %s, got %s\n", + domain_name, domain)); + return False; + } + + p = dcname; + if (*p == '\\') p += 1; + if (*p == '\\') p += 1; + + fstrcpy(dc_name, p); + + DEBUG(10, ("GetDC gave name %s for domain %s\n", + dc_name, domain)); + + return True; +} + +/******************************************************************* + convert an ip to a name +*******************************************************************/ + +static BOOL dcip_to_name(const struct winbindd_domain *domain, struct in_addr ip, fstring name ) +{ + struct ip_service ip_list; + + ip_list.ip = ip; + ip_list.port = 0; + +#ifdef WITH_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) { + ADS_STRUCT *ads; + + ads = ads_init(domain->alt_name, domain->name, NULL); + ads->auth.flags |= ADS_AUTH_NO_BIND; + + if (ads_try_connect( ads, inet_ntoa(ip) ) ) { + /* We got a cldap packet. */ + fstrcpy(name, ads->config.ldap_server_name); + namecache_store(name, 0x20, 1, &ip_list); + + DEBUG(10,("dcip_to_name: flags = 0x%x\n", (unsigned int)ads->config.flags)); + + if (domain->primary && (ads->config.flags & ADS_KDC)) { + if (ads_closest_dc(ads)) { + char *sitename = sitename_fetch(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, + ip); + + SAFE_FREE(sitename); + } else { + /* use an off site KDC */ + create_local_private_krb5_conf_for_domain(domain->alt_name, + domain->name, + NULL, + ip); + } + winbindd_set_locator_kdc_envs(domain); + + /* Ensure we contact this DC also. */ + saf_store( domain->name, name); + saf_store( domain->alt_name, name); + } + + ads_destroy( &ads ); + return True; + } + + ads_destroy( &ads ); + } +#endif + + /* try GETDC requests next */ + + if (send_getdc_request(ip, domain->name, &domain->sid)) { + int i; + smb_msleep(100); + for (i=0; i<5; i++) { + if (receive_getdc_response(ip, domain->name, name)) { + namecache_store(name, 0x20, 1, &ip_list); + return True; + } + smb_msleep(500); + } + } + + /* try node status request */ + + if ( name_status_find(domain->name, 0x1c, 0x20, ip, name) ) { + namecache_store(name, 0x20, 1, &ip_list); + return True; + } + return False; +} + +/******************************************************************* + Retreive a list of IP address for domain controllers. Fill in + the dcs[] with results. +*******************************************************************/ + +static BOOL get_dcs(TALLOC_CTX *mem_ctx, const struct winbindd_domain *domain, + struct dc_name_ip **dcs, int *num_dcs) +{ + fstring dcname; + struct in_addr ip; + struct ip_service *ip_list = NULL; + int iplist_size = 0; + int i; + BOOL is_our_domain; + enum security_types sec = (enum security_types)lp_security(); + + is_our_domain = strequal(domain->name, lp_workgroup()); + + if ( !is_our_domain + && get_dc_name_via_netlogon(domain, dcname, &ip) + && add_one_dc_unique(mem_ctx, domain->name, dcname, ip, dcs, num_dcs) ) + { + DEBUG(10, ("Retrieved DC %s at %s via netlogon\n", + dcname, inet_ntoa(ip))); + return True; + } + + if (sec == SEC_ADS) { + 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, &ip); + + sitename = sitename_fetch(domain->alt_name); + if (sitename) { + + /* Do the site-specific AD dns lookup first. */ + get_sorted_dc_list(domain->alt_name, sitename, &ip_list, &iplist_size, True); + + for ( i=0; iname, inet_ntoa(ip_list[i].ip), + ip_list[i].ip, dcs, num_dcs); + } + + SAFE_FREE(ip_list); + SAFE_FREE(sitename); + iplist_size = 0; + } + + /* Now we add DCs from the main AD dns lookup. */ + get_sorted_dc_list(domain->alt_name, NULL, &ip_list, &iplist_size, True); + + for ( i=0; iname, inet_ntoa(ip_list[i].ip), + ip_list[i].ip, dcs, num_dcs); + } + } + + /* try standard netbios queries if no ADS */ + + if (iplist_size==0) { + get_sorted_dc_list(domain->name, NULL, &ip_list, &iplist_size, False); + } + + /* FIXME!! this is where we should re-insert the GETDC requests --jerry */ + + /* now add to the dc array. We'll wait until the last minute + to look up the name of the DC. But we fill in the char* for + the ip now in to make the failed connection cache work */ + + for ( i=0; iname, inet_ntoa(ip_list[i].ip), + ip_list[i].ip, dcs, num_dcs); + } + + SAFE_FREE( ip_list ); + + return True; +} + +static BOOL find_new_dc(TALLOC_CTX *mem_ctx, + const struct winbindd_domain *domain, + fstring dcname, struct sockaddr_in *addr, int *fd) +{ + struct dc_name_ip *dcs = NULL; + int num_dcs = 0; + + const char **dcnames = NULL; + int num_dcnames = 0; + + struct sockaddr_in *addrs = NULL; + int num_addrs = 0; + + int i, fd_index; + + again: + if (!get_dcs(mem_ctx, domain, &dcs, &num_dcs) || (num_dcs == 0)) + return False; + + for (i=0; iname, inet_ntoa(dcs[i].ip), strerror(errno) )); + winbind_add_failed_connection_entry(domain, + dcs[i].name, NT_STATUS_UNSUCCESSFUL); + } + return False; + } + + *addr = addrs[fd_index]; + + if (*dcnames[fd_index] != '\0' && !is_ipaddress(dcnames[fd_index])) { + /* Ok, we've got a name for the DC */ + fstrcpy(dcname, dcnames[fd_index]); + return True; + } + + /* Try to figure out the name */ + if (dcip_to_name( domain, addr->sin_addr, dcname )) { + return True; + } + + /* We can not continue without the DC's name */ + winbind_add_failed_connection_entry(domain, dcs[fd_index].name, + NT_STATUS_UNSUCCESSFUL); + goto again; +} + +static NTSTATUS cm_open_connection(struct winbindd_domain *domain, + struct winbindd_cm_conn *new_conn) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS result; + char *saf_servername = saf_fetch( domain->name ); + int retries; + + if ((mem_ctx = talloc_init("cm_open_connection")) == NULL) { + SAFE_FREE(saf_servername); + set_domain_offline(domain); + return NT_STATUS_NO_MEMORY; + } + + /* we have to check the server affinity cache here since + later we selecte a DC based on response time and not preference */ + + /* Check the negative connection cache + before talking to it. It going down may have + triggered the reconnection. */ + + if ( saf_servername && NT_STATUS_IS_OK(check_negative_conn_cache( domain->name, saf_servername))) { + + DEBUG(10,("cm_open_connection: saf_servername is '%s' for domain %s\n", + saf_servername, domain->name )); + + /* convert an ip address to a name */ + if ( is_ipaddress( saf_servername ) ) { + fstring saf_name; + struct in_addr ip; + + ip = *interpret_addr2( saf_servername ); + if (dcip_to_name( domain, ip, saf_name )) { + fstrcpy( domain->dcname, saf_name ); + } else { + winbind_add_failed_connection_entry( + domain, saf_servername, + NT_STATUS_UNSUCCESSFUL); + } + } else { + fstrcpy( domain->dcname, saf_servername ); + } + + SAFE_FREE( saf_servername ); + } + + for (retries = 0; retries < 3; retries++) { + + int fd = -1; + BOOL retry = False; + + result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + + DEBUG(10,("cm_open_connection: dcname is '%s' for domain %s\n", + domain->dcname, domain->name )); + + if (*domain->dcname + && NT_STATUS_IS_OK(check_negative_conn_cache( domain->name, domain->dcname)) + && (resolve_name(domain->dcname, &domain->dcaddr.sin_addr, 0x20))) + { + struct sockaddr_in *addrs = NULL; + int num_addrs = 0; + int dummy = 0; + + if (!add_sockaddr_to_array(mem_ctx, domain->dcaddr.sin_addr, 445, &addrs, &num_addrs)) { + set_domain_offline(domain); + talloc_destroy(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + if (!add_sockaddr_to_array(mem_ctx, domain->dcaddr.sin_addr, 139, &addrs, &num_addrs)) { + set_domain_offline(domain); + talloc_destroy(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* 5 second timeout. */ + if (!open_any_socket_out(addrs, num_addrs, 5000, &dummy, &fd)) { + fd = -1; + } + } + + if ((fd == -1) + && !find_new_dc(mem_ctx, domain, domain->dcname, &domain->dcaddr, &fd)) + { + /* 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(); + break; + } + + new_conn->cli = NULL; + + result = cm_prepare_connection(domain, fd, domain->dcname, + &new_conn->cli, &retry); + + if (!retry) + break; + } + + if (NT_STATUS_IS_OK(result)) { + + 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); + } else { + /* Ensure we setup the retry handler. */ + set_domain_offline(domain); + } + + talloc_destroy(mem_ctx); + return result; +} + +/* Close down all open pipes on a connection. */ + +void invalidate_cm_connection(struct winbindd_cm_conn *conn) +{ + /* 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 (!cli_rpc_pipe_close(conn->samr_pipe)) { + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + conn->samr_pipe = NULL; + } + + if (conn->lsa_pipe != NULL) { + if (!cli_rpc_pipe_close(conn->lsa_pipe)) { + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + conn->lsa_pipe = NULL; + } + + if (conn->netlogon_pipe != NULL) { + if (!cli_rpc_pipe_close(conn->netlogon_pipe)) { + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + conn->netlogon_pipe = NULL; + } + + if (conn->cli) { + cli_shutdown(conn->cli); + } + + conn->cli = NULL; +} + +void close_conns_after_fork(void) +{ + struct winbindd_domain *domain; + + for (domain = domain_list(); domain; domain = domain->next) { + if (domain->conn.cli == NULL) + continue; + + if (domain->conn.cli->fd == -1) + continue; + + close(domain->conn.cli->fd); + domain->conn.cli->fd = -1; + } +} + +static BOOL connection_ok(struct winbindd_domain *domain) +{ + if (domain->conn.cli == NULL) { + DEBUG(8, ("connection_ok: Connection to %s for domain %s has NULL " + "cli!\n", domain->dcname, domain->name)); + return False; + } + + if (!domain->conn.cli->initialised) { + DEBUG(3, ("connection_ok: Connection to %s for domain %s was never " + "initialised!\n", domain->dcname, domain->name)); + return False; + } + + if (domain->conn.cli->fd == -1) { + DEBUG(3, ("connection_ok: Connection to %s for domain %s has died or was " + "never started (fd == -1)\n", + domain->dcname, domain->name)); + return False; + } + + if (domain->online == False) { + 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) +{ + NTSTATUS result; + + /* Internal connections never use the network. */ + if (domain->internal) { + domain->initialized = True; + return NT_STATUS_OK; + } + + if (connection_ok(domain)) { + if (!domain->initialized) { + set_dc_type_and_flags(domain); + } + return NT_STATUS_OK; + } + + invalidate_cm_connection(&domain->conn); + + result = cm_open_connection(domain, &domain->conn); + + if (NT_STATUS_IS_OK(result) && !domain->initialized) { + set_dc_type_and_flags(domain); + } + + return result; +} + +NTSTATUS init_dc_connection(struct winbindd_domain *domain) +{ + if (domain->initialized && !domain->online) { + /* We check for online status elsewhere. */ + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + } + + return init_dc_connection_network(domain); +} + +/****************************************************************************** + 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; + struct ds_domain_trust *domains = NULL; + int count = 0; + int i; + uint32 flags = (DS_DOMAIN_IN_FOREST | + DS_DOMAIN_DIRECT_OUTBOUND | + DS_DOMAIN_DIRECT_INBOUND); + struct rpc_pipe_client *cli; + TALLOC_CTX *mem_ctx = NULL; + + 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; + } + + our_domain = find_our_domain(); + + if ( !connection_ok(our_domain) ) { + DEBUG(3,("set_dc_type_and_flags_trustinfo: No connection to our domain!\n")); + return False; + } + + /* This won't work unless our domain is AD */ + + if ( !our_domain->active_directory ) { + return False; + } + + /* Use DsEnumerateDomainTrusts to get us the trust direction + and type */ + + 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))); + return False; + } + + if ( (mem_ctx = talloc_init("set_dc_type_and_flags_trustinfo")) == NULL ) { + DEBUG(0,("set_dc_type_and_flags_trustinfo: talloc_init() failed!\n")); + return False; + } + + result = rpccli_ds_enum_domain_trusts(cli, mem_ctx, + cli->cli->desthost, + flags, &domains, + (unsigned int *)&count); + + /* Now find the domain name and get the flags */ + + for ( i=0; iname, domains[i].netbios_domain ) ) { + domain->domain_flags = domains[i].flags; + domain->domain_type = domains[i].trust_type; + domain->domain_trust_attribs = domains[i].trust_attributes; + + if ( domain->domain_type == DS_DOMAIN_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 & DS_DOMAIN_NATIVE_MODE); + + 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->initialized = True; + + if ( !winbindd_can_contact_domain( domain) ) + domain->internal = True; + + break; + } + } + + talloc_destroy( 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 result; + DS_DOMINFO_CTR ctr; + TALLOC_CTX *mem_ctx = NULL; + struct rpc_pipe_client *cli; + POLICY_HND pol; + + char *domain_name = NULL; + char *dns_name = NULL; + char *forest_name = NULL; + DOM_SID *dom_sid = NULL; + + ZERO_STRUCT( ctr ); + + if (!connection_ok(domain)) { + return; + } + + DEBUG(5, ("set_dc_type_and_flags_connect: domain %s\n", domain->name )); + + cli = cli_rpc_pipe_open_noauth(domain->conn.cli, PI_LSARPC_DS, + &result); + + if (cli == NULL) { + DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to " + "PI_LSARPC_DS on domain %s: (%s)\n", + domain->name, nt_errstr(result))); + + /* 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_lsarpc_ds; + } + + result = rpccli_ds_getprimarydominfo(cli, cli->cli->mem_ctx, + DsRolePrimaryDomainInfoBasic, + &ctr); + cli_rpc_pipe_close(cli); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(5, ("set_dc_type_and_flags_connect: rpccli_ds_getprimarydominfo " + "on domain %s failed: (%s)\n", + domain->name, nt_errstr(result))); + + /* older samba3 DCs will return DCERPC_FAULT_OP_RNG_ERROR for + * every opcode on the LSARPC_DS pipe, continue with + * no_lsarpc_ds mode here as well to get domain->initialized + * set - gd */ + + if (NT_STATUS_V(result) == DCERPC_FAULT_OP_RNG_ERROR) { + goto no_lsarpc_ds; + } + + return; + } + + if ((ctr.basic->flags & DSROLE_PRIMARY_DS_RUNNING) && + !(ctr.basic->flags & DSROLE_PRIMARY_DS_MIXED_MODE)) { + domain->native_mode = True; + } else { + domain->native_mode = False; + } + +no_lsarpc_ds: + cli = cli_rpc_pipe_open_noauth(domain->conn.cli, PI_LSARPC, &result); + + if (cli == NULL) { + DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to " + "PI_LSARPC on domain %s: (%s)\n", + domain->name, nt_errstr(result))); + cli_rpc_pipe_close(cli); + 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")); + cli_rpc_pipe_close(cli); + return; + } + + result = rpccli_lsa_open_policy2(cli, mem_ctx, True, + SEC_RIGHTS_MAXIMUM_ALLOWED, &pol); + + if (NT_STATUS_IS_OK(result)) { + /* This particular query is exactly what Win2k clients use + to determine that the DC is active directory */ + result = rpccli_lsa_query_info_policy2(cli, mem_ctx, &pol, + 12, &domain_name, + &dns_name, &forest_name, + NULL, &dom_sid); + } + + if (NT_STATUS_IS_OK(result)) { + domain->active_directory = True; + + if (domain_name) + fstrcpy(domain->name, domain_name); + + if (dns_name) + fstrcpy(domain->alt_name, dns_name); + + if ( forest_name ) + fstrcpy(domain->forest_name, forest_name); + + if (dom_sid) + sid_copy(&domain->sid, dom_sid); + } else { + domain->active_directory = False; + + result = rpccli_lsa_open_policy(cli, mem_ctx, True, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &pol); + + if (!NT_STATUS_IS_OK(result)) + goto done; + + result = rpccli_lsa_query_info_policy(cli, mem_ctx, + &pol, 5, &domain_name, + &dom_sid); + + if (NT_STATUS_IS_OK(result)) { + if (domain_name) + fstrcpy(domain->name, domain_name); + + if (dom_sid) + sid_copy(&domain->sid, dom_sid); + } + } +done: + + 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 ")); + + cli_rpc_pipe_close(cli); + + talloc_destroy(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 ) +{ + /* we always have to contact our primary domain */ + + if ( domain->primary ) { + DEBUG(10,("set_dc_type_and_flags: setting up flags for " + "primary 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 BOOL cm_get_schannel_dcinfo(struct winbindd_domain *domain, + struct dcinfo **ppdc) +{ + NTSTATUS result; + struct rpc_pipe_client *netlogon_pipe; + + if (lp_client_schannel() == False) { + return False; + } + + result = cm_connect_netlogon(domain, &netlogon_pipe); + if (!NT_STATUS_IS_OK(result)) { + return False; + } + + /* Return a pointer to the struct dcinfo from the + netlogon pipe. */ + + *ppdc = domain->conn.netlogon_pipe->dc; + return True; +} + +NTSTATUS cm_connect_sam(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli, POLICY_HND *sam_handle) +{ + struct winbindd_cm_conn *conn; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + fstring conn_pwd; + struct dcinfo *p_dcinfo; + + result = init_dc_connection(domain); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + conn = &domain->conn; + + if (conn->samr_pipe != NULL) { + goto done; + } + + /* + * 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. + */ + + pwd_get_cleartext(&conn->cli->pwd, conn_pwd); + if ((conn->cli->user_name[0] == '\0') || + (conn->cli->domain[0] == '\0') || + (conn_pwd[0] == '\0')) { + DEBUG(10, ("cm_connect_sam: No no user available for " + "domain %s, trying schannel\n", conn->cli->domain)); + goto schannel; + } + + /* We have an authenticated connection. Use a NTLMSSP SPNEGO + authenticated SAMR pipe with sign & seal. */ + conn->samr_pipe = + cli_rpc_pipe_open_spnego_ntlmssp(conn->cli, PI_SAMR, + PIPE_AUTH_LEVEL_PRIVACY, + conn->cli->domain, + conn->cli->user_name, + conn_pwd, &result); + + if (conn->samr_pipe == NULL) { + DEBUG(10,("cm_connect_sam: failed to connect to SAMR " + "pipe for domain %s using NTLMSSP " + "authenticated pipe: user %s\\%s. Error was " + "%s\n", domain->name, conn->cli->domain, + conn->cli->user_name, nt_errstr(result))); + goto schannel; + } + + DEBUG(10,("cm_connect_sam: connected to SAMR pipe for " + "domain %s using NTLMSSP authenticated " + "pipe: user %s\\%s\n", domain->name, + conn->cli->domain, conn->cli->user_name )); + + result = rpccli_samr_connect(conn->samr_pipe, mem_ctx, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &conn->sam_connect_handle); + if (NT_STATUS_IS_OK(result)) { + goto open_domain; + } + DEBUG(10,("cm_connect_sam: ntlmssp-sealed rpccli_samr_connect " + "failed for domain %s, error was %s. Trying schannel\n", + domain->name, nt_errstr(result) )); + cli_rpc_pipe_close(conn->samr_pipe); + + schannel: + + /* Fall back to schannel if it's a W2K pre-SP1 box. */ + + if (!cm_get_schannel_dcinfo(domain, &p_dcinfo)) { + /* If this call fails - conn->cli can now be NULL ! */ + DEBUG(10, ("cm_connect_sam: Could not get schannel auth info " + "for domain %s, trying anon\n", domain->name)); + goto anonymous; + } + conn->samr_pipe = cli_rpc_pipe_open_schannel_with_key + (conn->cli, PI_SAMR, PIPE_AUTH_LEVEL_PRIVACY, + domain->name, p_dcinfo, &result); + + if (conn->samr_pipe == NULL) { + DEBUG(10,("cm_connect_sam: failed to connect to SAMR pipe for " + "domain %s using schannel. Error was %s\n", + domain->name, nt_errstr(result) )); + goto anonymous; + } + DEBUG(10,("cm_connect_sam: connected to SAMR pipe for domain %s using " + "schannel.\n", domain->name )); + + result = rpccli_samr_connect(conn->samr_pipe, mem_ctx, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &conn->sam_connect_handle); + if (NT_STATUS_IS_OK(result)) { + goto open_domain; + } + DEBUG(10,("cm_connect_sam: schannel-sealed rpccli_samr_connect failed " + "for domain %s, error was %s. Trying anonymous\n", + domain->name, nt_errstr(result) )); + cli_rpc_pipe_close(conn->samr_pipe); + + anonymous: + + /* Finally fall back to anonymous. */ + conn->samr_pipe = cli_rpc_pipe_open_noauth(conn->cli, PI_SAMR, + &result); + + if (conn->samr_pipe == NULL) { + result = NT_STATUS_PIPE_NOT_AVAILABLE; + goto done; + } + + result = rpccli_samr_connect(conn->samr_pipe, mem_ctx, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &conn->sam_connect_handle); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("cm_connect_sam: rpccli_samr_connect failed " + "for domain %s Error was %s\n", + domain->name, nt_errstr(result) )); + goto done; + } + + open_domain: + result = rpccli_samr_open_domain(conn->samr_pipe, + mem_ctx, + &conn->sam_connect_handle, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &domain->sid, + &conn->sam_domain_handle); + + done: + + if (!NT_STATUS_IS_OK(result)) { + invalidate_cm_connection(conn); + return result; + } + + *cli = conn->samr_pipe; + *sam_handle = conn->sam_domain_handle; + return result; +} + +NTSTATUS cm_connect_lsa(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli, POLICY_HND *lsa_policy) +{ + struct winbindd_cm_conn *conn; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + fstring conn_pwd; + struct dcinfo *p_dcinfo; + + result = init_dc_connection(domain); + if (!NT_STATUS_IS_OK(result)) + return result; + + conn = &domain->conn; + + if (conn->lsa_pipe != NULL) { + goto done; + } + + pwd_get_cleartext(&conn->cli->pwd, conn_pwd); + if ((conn->cli->user_name[0] == '\0') || + (conn->cli->domain[0] == '\0') || + (conn_pwd[0] == '\0')) { + DEBUG(10, ("cm_connect_lsa: No no user available for " + "domain %s, trying schannel\n", conn->cli->domain)); + goto schannel; + } + + /* We have an authenticated connection. Use a NTLMSSP SPNEGO + * authenticated LSA pipe with sign & seal. */ + conn->lsa_pipe = cli_rpc_pipe_open_spnego_ntlmssp + (conn->cli, PI_LSARPC, PIPE_AUTH_LEVEL_PRIVACY, + conn->cli->domain, conn->cli->user_name, conn_pwd, &result); + + if (conn->lsa_pipe == NULL) { + DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for " + "domain %s using NTLMSSP authenticated pipe: user " + "%s\\%s. Error was %s. Trying schannel.\n", + domain->name, conn->cli->domain, + conn->cli->user_name, nt_errstr(result))); + goto schannel; + } + + DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using " + "NTLMSSP authenticated pipe: user %s\\%s\n", + domain->name, conn->cli->domain, conn->cli->user_name )); + + result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &conn->lsa_policy); + if (NT_STATUS_IS_OK(result)) { + goto done; + } + + DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying " + "schannel\n")); + + cli_rpc_pipe_close(conn->lsa_pipe); + + schannel: + + /* Fall back to schannel if it's a W2K pre-SP1 box. */ + + if (!cm_get_schannel_dcinfo(domain, &p_dcinfo)) { + /* If this call fails - conn->cli can now be NULL ! */ + DEBUG(10, ("cm_connect_lsa: Could not get schannel auth info " + "for domain %s, trying anon\n", domain->name)); + goto anonymous; + } + conn->lsa_pipe = cli_rpc_pipe_open_schannel_with_key + (conn->cli, PI_LSARPC, PIPE_AUTH_LEVEL_PRIVACY, + domain->name, p_dcinfo, &result); + + if (conn->lsa_pipe == NULL) { + 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_RIGHTS_MAXIMUM_ALLOWED, + &conn->lsa_policy); + if (NT_STATUS_IS_OK(result)) { + goto done; + } + + DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying " + "anonymous\n")); + + cli_rpc_pipe_close(conn->lsa_pipe); + + anonymous: + + conn->lsa_pipe = cli_rpc_pipe_open_noauth(conn->cli, PI_LSARPC, + &result); + if (conn->lsa_pipe == NULL) { + result = NT_STATUS_PIPE_NOT_AVAILABLE; + goto done; + } + + result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True, + SEC_RIGHTS_MAXIMUM_ALLOWED, + &conn->lsa_policy); + done: + if (!NT_STATUS_IS_OK(result)) { + invalidate_cm_connection(conn); + return result; + } + + *cli = conn->lsa_pipe; + *lsa_policy = conn->lsa_policy; + return result; +} + +/**************************************************************************** + Open the netlogon pipe to this DC. Use schannel if specified in client conf. + session key stored in conn->netlogon_pipe->dc->sess_key. +****************************************************************************/ + +NTSTATUS cm_connect_netlogon(struct winbindd_domain *domain, + struct rpc_pipe_client **cli) +{ + struct winbindd_cm_conn *conn; + NTSTATUS result; + + uint32 neg_flags = NETLOGON_NEG_AUTH2_FLAGS; + uint8 mach_pwd[16]; + uint32 sec_chan_type; + const char *account_name; + struct rpc_pipe_client *netlogon_pipe = NULL; + + *cli = NULL; + + result = init_dc_connection(domain); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + conn = &domain->conn; + + if (conn->netlogon_pipe != NULL) { + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; + } + + if ((IS_DC || domain->primary) && !get_trust_pw(domain->name, mach_pwd, &sec_chan_type)) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + netlogon_pipe = cli_rpc_pipe_open_noauth(conn->cli, PI_NETLOGON, + &result); + if (netlogon_pipe == NULL) { + return result; + } + + if ((!IS_DC) && (!domain->primary)) { + /* Clear the schannel request bit and drop down */ + neg_flags &= ~NETLOGON_NEG_SCHANNEL; + goto no_schannel; + } + + if (lp_client_schannel() != False) { + neg_flags |= NETLOGON_NEG_SCHANNEL; + } + + /* if we are a DC and this is a trusted domain, then we need to use our + domain name in the net_req_auth2() request */ + + if ( IS_DC + && !strequal(domain->name, lp_workgroup()) + && lp_allow_trusted_domains() ) + { + account_name = lp_workgroup(); + } else { + account_name = domain->primary ? + global_myname() : domain->name; + } + + if (account_name == NULL) { + cli_rpc_pipe_close(netlogon_pipe); + return NT_STATUS_NO_MEMORY; + } + + result = rpccli_netlogon_setup_creds( + netlogon_pipe, + domain->dcname, /* server name. */ + domain->name, /* domain name */ + global_myname(), /* client name */ + account_name, /* machine account */ + mach_pwd, /* machine password */ + sec_chan_type, /* from get_trust_pw */ + &neg_flags); + + if (!NT_STATUS_IS_OK(result)) { + cli_rpc_pipe_close(netlogon_pipe); + return result; + } + + if ((lp_client_schannel() == True) && + ((neg_flags & NETLOGON_NEG_SCHANNEL) == 0)) { + DEBUG(3, ("Server did not offer schannel\n")); + cli_rpc_pipe_close(netlogon_pipe); + return NT_STATUS_ACCESS_DENIED; + } + + no_schannel: + if ((lp_client_schannel() == False) || + ((neg_flags & NETLOGON_NEG_SCHANNEL) == 0)) { + + /* + * NetSamLogonEx only works for schannel + */ + domain->can_do_samlogon_ex = False; + + /* We're done - just keep the existing connection to NETLOGON + * open */ + conn->netlogon_pipe = netlogon_pipe; + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; + } + + /* Using the credentials from the first pipe, open a signed and sealed + second netlogon pipe. The session key is stored in the schannel + part of the new pipe auth struct. + */ + + conn->netlogon_pipe = + cli_rpc_pipe_open_schannel_with_key(conn->cli, + PI_NETLOGON, + PIPE_AUTH_LEVEL_PRIVACY, + domain->name, + netlogon_pipe->dc, + &result); + + /* We can now close the initial netlogon pipe. */ + cli_rpc_pipe_close(netlogon_pipe); + + if (conn->netlogon_pipe == NULL) { + DEBUG(3, ("Could not open schannel'ed NETLOGON pipe. Error " + "was %s\n", nt_errstr(result))); + + /* make sure we return something besides OK */ + return !NT_STATUS_IS_OK(result) ? result : NT_STATUS_PIPE_NOT_AVAILABLE; + } + + /* + * Try NetSamLogonEx for AD domains + */ + domain->can_do_samlogon_ex = domain->active_directory; + + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; +} -- cgit