X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source3%2Fwinbindd%2Fwinbindd_util.c;h=73e6b76ec733ffc08f3b2914026291445c41e9e2;hb=7f2d45a6c2a88dd8833fc66d314ec21507dd52c3;hp=85b014d97ccc5965ca423b66a948205ce50f259e;hpb=f087a8e2b81fae82fa571ef09d2d1cb682cc8ff8;p=samba.git diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c index 85b014d97cc..73e6b76ec73 100644 --- a/source3/winbindd/winbindd_util.c +++ b/source3/winbindd/winbindd_util.c @@ -22,22 +22,28 @@ #include "includes.h" #include "winbindd.h" +#include "lib/util_unixsids.h" #include "secrets.h" #include "../libcli/security/security.h" #include "../libcli/auth/pam_errors.h" #include "passdb/machine_sid.h" +#include "passdb.h" +#include "source4/lib/messaging/messaging.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "auth/credentials/credentials.h" +#include "libsmb/samlogon_cache.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_WINBIND -extern struct winbindd_methods cache_methods; - /** - * @file winbindd_util.cq + * @file winbindd_util.c * * Winbind daemon for NT domain authentication nss module. **/ +static bool add_trusted_domains_dc(void); /* The list of trusted domains. Note that the list can be deleted and recreated using the init_domain_list() function so pointers to @@ -72,6 +78,28 @@ static void free_domain_list(void) } } +/** + * Iterator for winbindd's domain list. + * To be used (e.g.) in tevent based loops. + */ +struct winbindd_domain *wb_next_domain(struct winbindd_domain *domain) +{ + if (domain == NULL) { + domain = domain_list(); + } else { + domain = domain->next; + } + + if ((domain != NULL) && + (lp_server_role() != ROLE_ACTIVE_DIRECTORY_DC) && + sid_check_is_our_sam(&domain->sid)) + { + domain = domain->next; + } + + return domain; +} + static bool is_internal_domain(const struct dom_sid *sid) { if (sid == NULL) @@ -89,76 +117,107 @@ static bool is_in_internal_domain(const struct dom_sid *sid) } -/* Add a trusted domain to our list of domains */ -static struct winbindd_domain *add_trusted_domain(const char *domain_name, const char *alt_name, - struct winbindd_methods *methods, - const struct dom_sid *sid) +/* Add a trusted domain to our list of domains. + If the domain already exists in the list, + return it and don't re-initialize. */ + +static NTSTATUS add_trusted_domain(const char *domain_name, + const char *dns_name, + const struct dom_sid *sid, + uint32_t trust_type, + uint32_t trust_flags, + uint32_t trust_attribs, + enum netr_SchannelType secure_channel_type, + struct winbindd_domain *routing_domain, + struct winbindd_domain **_d) { - struct winbindd_domain *domain; - const char *alternative_name = NULL; - char *idmap_config_option; - const char *param; - const char **ignored_domains, **dom; + struct winbindd_domain *domain = NULL; + const char **ignored_domains = NULL; + const char **dom = NULL; + int role = lp_server_role(); + + if (is_null_sid(sid)) { + DBG_ERR("Got null SID for domain [%s]\n", domain_name); + return NT_STATUS_INVALID_PARAMETER; + } ignored_domains = lp_parm_string_list(-1, "winbind", "ignore domains", NULL); for (dom=ignored_domains; dom && *dom; dom++) { if (gen_fnmatch(*dom, domain_name) == 0) { DEBUG(2,("Ignoring domain '%s'\n", domain_name)); - return NULL; + return NT_STATUS_NO_SUCH_DOMAIN; } } - /* use alt_name if available to allow DNS lookups */ - - if (alt_name && *alt_name) { - alternative_name = alt_name; - } - - /* We can't call domain_list() as this function is called from - init_domain_list() and we'll get stuck in a loop. */ + /* + * We can't call domain_list() as this function is called from + * init_domain_list() and we'll get stuck in a loop. + */ for (domain = _domain_list; domain; domain = domain->next) { - if (strequal(domain_name, domain->name) || - strequal(domain_name, domain->alt_name)) - { + if (strequal(domain_name, domain->name)) { break; } + } + + if (domain != NULL) { + struct winbindd_domain *check_domain = NULL; - if (alternative_name && *alternative_name) + for (check_domain = _domain_list; + check_domain != NULL; + check_domain = check_domain->next) { - if (strequal(alternative_name, domain->name) || - strequal(alternative_name, domain->alt_name)) - { + if (check_domain == domain) { + continue; + } + + if (dom_sid_equal(&check_domain->sid, sid)) { break; } } - if (sid) + if (check_domain != NULL) { + DBG_ERR("SID [%s] already used by domain [%s], " + "expected [%s]\n", + sid_string_dbg(sid), check_domain->name, + domain->name); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if ((domain != NULL) && (dns_name != NULL)) { + struct winbindd_domain *check_domain = NULL; + + for (check_domain = _domain_list; + check_domain != NULL; + check_domain = check_domain->next) { - if (is_null_sid(sid)) { + if (check_domain == domain) { continue; } - if (dom_sid_equal(sid, &domain->sid)) { + if (strequal(check_domain->alt_name, dns_name)) { break; } } + + if (check_domain != NULL) { + DBG_ERR("DNS name [%s] used by domain [%s], " + "expected [%s]\n", + dns_name, check_domain->name, + domain->name); + return NT_STATUS_INVALID_PARAMETER; + } } if (domain != NULL) { - /* - * We found a match. Possibly update the SID - */ - if ((sid != NULL) - && dom_sid_equal(&domain->sid, &global_sid_NULL)) { - sid_copy( &domain->sid, sid ); - } - return domain; + *_d = domain; + return NT_STATUS_OK; } /* Create new domain entry */ domain = talloc_zero(NULL, struct winbindd_domain); if (domain == NULL) { - return NULL; + return NT_STATUS_NO_MEMORY; } domain->children = talloc_zero_array(domain, @@ -166,77 +225,152 @@ static struct winbindd_domain *add_trusted_domain(const char *domain_name, const lp_winbind_max_domain_connections()); if (domain->children == NULL) { TALLOC_FREE(domain); - return NULL; + return NT_STATUS_NO_MEMORY; + } + + domain->queue = tevent_queue_create(domain, "winbind_domain"); + if (domain->queue == NULL) { + TALLOC_FREE(domain); + return NT_STATUS_NO_MEMORY; + } + + domain->binding_handle = wbint_binding_handle(domain, domain, NULL); + if (domain->binding_handle == NULL) { + TALLOC_FREE(domain); + return NT_STATUS_NO_MEMORY; } domain->name = talloc_strdup(domain, domain_name); if (domain->name == NULL) { TALLOC_FREE(domain); - return NULL; + return NT_STATUS_NO_MEMORY; } - if (alternative_name) { - domain->alt_name = talloc_strdup(domain, alternative_name); + if (dns_name != NULL) { + domain->alt_name = talloc_strdup(domain, dns_name); if (domain->alt_name == NULL) { TALLOC_FREE(domain); - return NULL; + return NT_STATUS_NO_MEMORY; } } - domain->methods = methods; domain->backend = NULL; domain->internal = is_internal_domain(sid); + domain->secure_channel_type = secure_channel_type; domain->sequence_number = DOM_SEQUENCE_NONE; domain->last_seq_check = 0; - domain->initialized = False; + domain->initialized = false; domain->online = is_internal_domain(sid); domain->check_online_timeout = 0; domain->dc_probe_pid = (pid_t)-1; - if (sid) { - sid_copy(&domain->sid, sid); + domain->domain_flags = trust_flags; + domain->domain_type = trust_type; + domain->domain_trust_attribs = trust_attribs; + domain->secure_channel_type = secure_channel_type; + domain->routing_domain = routing_domain; + sid_copy(&domain->sid, sid); + + /* Is this our primary domain ? */ + if (role == ROLE_DOMAIN_MEMBER) { + domain->primary = strequal(domain_name, lp_workgroup()); + } else { + domain->primary = strequal(domain_name, get_global_sam_name()); + } + + if (domain->primary) { + if (role == ROLE_ACTIVE_DIRECTORY_DC) { + domain->active_directory = true; + } + if (lp_security() == SEC_ADS) { + domain->active_directory = true; + } + } else if (!domain->internal) { + if (domain->domain_type == LSA_TRUST_TYPE_UPLEVEL) { + domain->active_directory = true; + } } + domain->can_do_ncacn_ip_tcp = domain->active_directory; + /* Link to domain list */ - DLIST_ADD_END(_domain_list, domain, struct winbindd_domain *); + DLIST_ADD_END(_domain_list, domain); wcache_tdc_add_domain( domain ); - idmap_config_option = talloc_asprintf(talloc_tos(), "idmap config %s", - domain->name); - if (idmap_config_option == NULL) { - DEBUG(0, ("talloc failed, not looking for idmap config\n")); - goto done; + setup_domain_child(domain); + + DBG_NOTICE("Added domain [%s] [%s] [%s]\n", + domain->name, domain->alt_name, + sid_string_dbg(&domain->sid)); + + *_d = domain; + return NT_STATUS_OK; +} + +bool set_routing_domain(struct winbindd_domain *domain, + struct winbindd_domain *routing_domain) +{ + if (domain->routing_domain == NULL) { + domain->routing_domain = routing_domain; + return true; } + if (domain->routing_domain != routing_domain) { + return false; + } + return true; +} - param = lp_parm_const_string(-1, idmap_config_option, "range", NULL); +bool add_trusted_domain_from_auth(uint16_t validation_level, + struct info3_text *info3, + struct info6_text *info6) +{ + struct winbindd_domain *domain = NULL; + struct dom_sid domain_sid; + const char *dns_domainname = NULL; + NTSTATUS status; + bool ok; - DEBUG(10, ("%s : range = %s\n", idmap_config_option, - param ? param : "not defined")); + /* + * We got a successfull auth from a domain that might not yet be in our + * domain list. If we're a member we trust our DC who authenticated the + * user from that domain and add the domain to our list on-the-fly. If + * we're a DC we rely on configured trusts and don't add on-the-fly. + */ - if (param != NULL) { - unsigned low_id, high_id; - if (sscanf(param, "%u - %u", &low_id, &high_id) != 2) { - DEBUG(1, ("invalid range syntax in %s: %s\n", - idmap_config_option, param)); - goto done; - } - if (low_id > high_id) { - DEBUG(1, ("invalid range in %s: %s\n", - idmap_config_option, param)); - goto done; - } - domain->have_idmap_config = true; - domain->id_range_low = low_id; - domain->id_range_high = high_id; + if (IS_DC) { + return true; } -done: + ok = dom_sid_parse(info3->dom_sid, &domain_sid); + if (!ok) { + DBG_NOTICE("dom_sid_parse [%s] failed\n", info3->dom_sid); + return false; + } - DEBUG(2,("Added domain %s %s %s\n", - domain->name, domain->alt_name, - &domain->sid?sid_string_dbg(&domain->sid):"")); + if (validation_level == 6) { + if (!strequal(info6->dns_domainname, "")) { + dns_domainname = info6->dns_domainname; + } + } - return domain; + status = add_trusted_domain(info3->logon_dom, + dns_domainname, + &domain_sid, + 0, + NETR_TRUST_FLAG_OUTBOUND, + 0, + SEC_CHAN_NULL, + find_default_route_domain(), + &domain); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) + { + DBG_DEBUG("Adding domain [%s] with sid [%s] failed\n", + info3->logon_dom, info3->dom_sid); + return false; + } + + return true; } bool domain_is_forest_root(const struct winbindd_domain *domain) @@ -275,7 +409,7 @@ static void add_trusted_domains( struct winbindd_domain *domain ) state->request.length = sizeof(state->request); state->request.cmd = WINBINDD_LIST_TRUSTDOM; - req = wb_domain_request_send(state, winbind_event_context(), + req = wb_domain_request_send(state, server_event_context(), domain, &state->request); if (req == NULL) { DEBUG(1, ("wb_domain_request_send failed\n")); @@ -292,26 +426,56 @@ static void trustdom_list_done(struct tevent_req *req) struct winbindd_response *response; int res, err; char *p; + ptrdiff_t extra_len; + bool within_forest = false; + NTSTATUS status; + + /* + * Only when we enumerate our primary domain + * or our forest root domain, we should keep + * the NETR_TRUST_FLAG_IN_FOREST flag, in + * all other cases we need to clear it as the domain + * is not part of our forest. + */ + if (state->domain->primary) { + within_forest = true; + } else if (domain_is_forest_root(state->domain)) { + within_forest = true; + } res = wb_domain_request_recv(req, state, &response, &err); if ((res == -1) || (response->result != WINBINDD_OK)) { - DEBUG(1, ("Could not receive trustdoms\n")); + DBG_WARNING("Could not receive trusts for domain %s\n", + state->domain->name); + TALLOC_FREE(state); + return; + } + + if (response->length < sizeof(struct winbindd_response)) { + DBG_ERR("ill-formed trustdom response - short length\n"); TALLOC_FREE(state); return; } + extra_len = response->length - sizeof(struct winbindd_response); + p = (char *)response->extra_data.data; - while ((p != NULL) && (*p != '\0')) { - char *q, *sidstr, *alt_name; + while ((p - (char *)response->extra_data.data) < extra_len) { + struct winbindd_domain *domain = NULL; + char *name, *q, *sidstr, *alt_name; struct dom_sid sid; - struct winbindd_domain *domain; - char *alternate_name = NULL; - bool domain_exists; + uint32_t trust_type; + uint32_t trust_attribs; + uint32_t trust_flags; + + DBG_DEBUG("parsing response line '%s'\n", p); + + name = p; alt_name = strchr(p, '\\'); if (alt_name == NULL) { - DEBUG(0, ("Got invalid trustdom response\n")); + DBG_ERR("Got invalid trustdom response\n"); break; } @@ -320,29 +484,60 @@ static void trustdom_list_done(struct tevent_req *req) sidstr = strchr(alt_name, '\\'); if (sidstr == NULL) { - DEBUG(0, ("Got invalid trustdom response\n")); + DBG_ERR("Got invalid trustdom response\n"); break; } *sidstr = '\0'; sidstr += 1; - q = strchr(sidstr, '\n'); - if (q != NULL) - *q = '\0'; + /* use the real alt_name if we have one, else pass in NULL */ + if (strequal(alt_name, "(null)")) { + alt_name = NULL; + } + + q = strtok(sidstr, "\\"); + if (q == NULL) { + DBG_ERR("Got invalid trustdom response\n"); + break; + } if (!string_to_sid(&sid, sidstr)) { DEBUG(0, ("Got invalid trustdom response\n")); break; } - /* use the real alt_name if we have one, else pass in NULL */ + q = strtok(NULL, "\\"); + if (q == NULL) { + DBG_ERR("Got invalid trustdom response\n"); + break; + } - if ( !strequal( alt_name, "(null)" ) ) - alternate_name = alt_name; + trust_flags = (uint32_t)strtoul(q, NULL, 10); - /* Check if we already have a child for the domain */ - domain_exists = (find_domain_from_name_noinit(p) != NULL); + q = strtok(NULL, "\\"); + if (q == NULL) { + DBG_ERR("Got invalid trustdom response\n"); + break; + } + + trust_type = (uint32_t)strtoul(q, NULL, 10); + + q = strtok(NULL, "\n"); + if (q == NULL) { + DBG_ERR("Got invalid trustdom response\n"); + break; + } + + trust_attribs = (uint32_t)strtoul(q, NULL, 10); + + if (!within_forest) { + trust_flags &= ~NETR_TRUST_FLAG_IN_FOREST; + } + + if (!state->domain->primary) { + trust_flags &= ~NETR_TRUST_FLAG_PRIMARY; + } /* * We always call add_trusted_domain() cause on an existing @@ -350,20 +545,24 @@ static void trustdom_list_done(struct tevent_req *req) * This is important because we need the SID for sibling * domains. */ - domain = add_trusted_domain(p, alternate_name, - &cache_methods, - &sid); - - /* - * If the domain doesn't exist yet and got correctly added, - * setup a new domain child. - */ - if (!domain_exists && domain != NULL) { - setup_domain_child(domain); + status = add_trusted_domain(name, + alt_name, + &sid, + trust_type, + trust_flags, + trust_attribs, + SEC_CHAN_NULL, + find_default_route_domain(), + &domain); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) + { + DBG_NOTICE("add_trusted_domain returned %s\n", + nt_errstr(status)); + return; } - p=q; - if (p != NULL) - p += 1; + + p = q + strlen(q) + 1; } /* @@ -404,6 +603,7 @@ static void rescan_forest_root_trusts( void ) struct winbindd_tdc_domain *dom_list = NULL; size_t num_trusts = 0; int i; + NTSTATUS status; /* The only transitive trusts supported by Windows 2003 AD are (a) Parent-Child, (b) Tree-Root, and (c) Forest. The @@ -428,17 +628,25 @@ static void rescan_forest_root_trusts( void ) /* Here's the forest root */ d = find_domain_from_name_noinit( dom_list[i].domain_name ); - - if ( !d ) { - d = add_trusted_domain( dom_list[i].domain_name, - dom_list[i].dns_name, - &cache_methods, - &dom_list[i].sid ); - if (d != NULL) { - setup_domain_child(d); + if (d == NULL) { + status = add_trusted_domain(dom_list[i].domain_name, + dom_list[i].dns_name, + &dom_list[i].sid, + dom_list[i].trust_type, + dom_list[i].trust_flags, + dom_list[i].trust_attribs, + SEC_CHAN_NULL, + find_default_route_domain(), + &d); + + if (!NT_STATUS_IS_OK(status) && + NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) + { + DBG_ERR("add_trusted_domain returned %s\n", + nt_errstr(status)); + return; } } - if (d == NULL) { continue; } @@ -472,6 +680,7 @@ static void rescan_forest_trusts( void ) struct winbindd_tdc_domain *dom_list = NULL; size_t num_trusts = 0; int i; + NTSTATUS status; /* The only transitive trusts supported by Windows 2003 AD are (a) Parent-Child, (b) Tree-Root, and (c) Forest. The @@ -483,9 +692,9 @@ static void rescan_forest_trusts( void ) return; for ( i=0; irequest->data.init_conn.dcname)-1]='\0'; if (strlen(state->request->data.init_conn.dcname) > 0) { - fstrcpy(domain->dcname, state->request->data.init_conn.dcname); + TALLOC_FREE(domain->dcname); + domain->dcname = talloc_strdup(domain, + state->request->data.init_conn.dcname); + if (domain->dcname == NULL) { + return WINBINDD_ERROR; + } } - if (domain->internal) { - domain->initialized = true; - } else { - init_dc_connection(domain); - } + init_dc_connection(domain, false); if (!domain->initialized) { /* If we return error here we can't do any cached authentication, @@ -606,58 +827,470 @@ enum winbindd_result winbindd_dual_init_connection(struct winbindd_domain *domai return WINBINDD_OK; } +static void wb_imsg_new_trusted_domain(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + bool ok; + + DBG_NOTICE("Rescanning trusted domains\n"); + + ok = add_trusted_domains_dc(); + if (!ok) { + DBG_ERR("Failed to reload trusted domains\n"); + } +} + +/* + * We did not get the secret when we queried secrets.tdb, so read it + * from secrets.tdb and re-sync the databases + */ +static bool migrate_secrets_tdb_to_ldb(struct winbindd_domain *domain) +{ + bool ok; + struct cli_credentials *creds; + NTSTATUS can_migrate = pdb_get_trust_credentials(domain->name, + NULL, domain, &creds); + if (!NT_STATUS_IS_OK(can_migrate)) { + DEBUG(0, ("Failed to fetch our own, local AD domain join " + "password for winbindd's internal use, both from " + "secrets.tdb and secrets.ldb: %s\n", + nt_errstr(can_migrate))); + return false; + } + + /* + * NOTE: It is very unlikely we end up here if there is an + * oldpass, because a new password is created at + * classicupgrade, so this is not a concern. + */ + ok = secrets_store_machine_pw_sync(cli_credentials_get_password(creds), + NULL /* oldpass */, + cli_credentials_get_domain(creds), + cli_credentials_get_realm(creds), + cli_credentials_get_salt_principal(creds), + 0, /* Supported enc types, unused */ + &domain->sid, + cli_credentials_get_password_last_changed_time(creds), + cli_credentials_get_secure_channel_type(creds), + false /* do_delete: Do not delete */); + TALLOC_FREE(creds); + if (ok == false) { + DEBUG(0, ("Failed to write our our own, " + "local AD domain join password for " + "winbindd's internal use into secrets.tdb\n")); + return false; + } + return true; +} + +static bool add_trusted_domains_dc(void) +{ + struct winbindd_domain *domain = NULL; + struct pdb_trusted_domain **domains = NULL; + uint32_t num_domains = 0; + uint32_t i; + NTSTATUS status; + + if (!(pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX)) { + struct trustdom_info **ti = NULL; + + status = pdb_enum_trusteddoms(talloc_tos(), &num_domains, &ti); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("pdb_enum_trusteddoms() failed - %s\n", + nt_errstr(status)); + return false; + } + + for (i = 0; i < num_domains; i++) { + status = add_trusted_domain(ti[i]->name, + NULL, + &ti[i]->sid, + LSA_TRUST_TYPE_DOWNLEVEL, + NETR_TRUST_FLAG_OUTBOUND, + 0, + SEC_CHAN_DOMAIN, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("add_trusted_domain returned %s\n", + nt_errstr(status)); + return false; + } + + /* Even in the parent winbindd we'll need to + talk to the DC, so try and see if we can + contact it. Theoretically this isn't neccessary + as the init_dc_connection() in init_child_recv() + will do this, but we can start detecting the DC + early here. */ + set_domain_online_request(domain); + } + + return true; + } + + status = pdb_enum_trusted_domains(talloc_tos(), &num_domains, &domains); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("pdb_enum_trusted_domains() failed - %s\n", + nt_errstr(status)); + return false; + } + + for (i = 0; i < num_domains; i++) { + enum netr_SchannelType sec_chan_type = SEC_CHAN_DOMAIN; + uint32_t trust_flags = 0; + + if (domains[i]->trust_type == LSA_TRUST_TYPE_UPLEVEL) { + sec_chan_type = SEC_CHAN_DNS_DOMAIN; + } + + if (!(domains[i]->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND)) { + sec_chan_type = SEC_CHAN_NULL; + } + + if (domains[i]->trust_direction & LSA_TRUST_DIRECTION_INBOUND) { + trust_flags |= NETR_TRUST_FLAG_INBOUND; + } + if (domains[i]->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND) { + trust_flags |= NETR_TRUST_FLAG_OUTBOUND; + } + if (domains[i]->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + trust_flags |= NETR_TRUST_FLAG_IN_FOREST; + } + + status = add_trusted_domain(domains[i]->netbios_name, + domains[i]->domain_name, + &domains[i]->security_identifier, + domains[i]->trust_type, + trust_flags, + domains[i]->trust_attributes, + sec_chan_type, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("add_trusted_domain returned %s\n", + nt_errstr(status)); + return false; + } + + if (domains[i]->trust_type == LSA_TRUST_TYPE_UPLEVEL) { + domain->active_directory = true; + } + domain->domain_type = domains[i]->trust_type; + domain->domain_trust_attribs = domains[i]->trust_attributes; + + if (sec_chan_type != SEC_CHAN_NULL) { + /* Even in the parent winbindd we'll need to + talk to the DC, so try and see if we can + contact it. Theoretically this isn't neccessary + as the init_dc_connection() in init_child_recv() + will do this, but we can start detecting the DC + early here. */ + set_domain_online_request(domain); + } + } + + for (i = 0; i < num_domains; i++) { + struct ForestTrustInfo fti; + uint32_t fi; + enum ndr_err_code ndr_err; + struct winbindd_domain *routing_domain = NULL; + + if (domains[i]->trust_type != LSA_TRUST_TYPE_UPLEVEL) { + continue; + } + + if (!(domains[i]->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) { + continue; + } + + if (domains[i]->trust_forest_trust_info.length == 0) { + continue; + } + + routing_domain = find_domain_from_name_noinit( + domains[i]->netbios_name); + if (routing_domain == NULL) { + DBG_ERR("Can't find winbindd domain [%s]\n", + domains[i]->netbios_name); + return false; + } + + ndr_err = ndr_pull_struct_blob_all( + &domains[i]->trust_forest_trust_info, + talloc_tos(), &fti, + (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_pull_ForestTrustInfo(%s) - %s\n", + domains[i]->netbios_name, + ndr_map_error2string(ndr_err)); + return false; + } + + for (fi = 0; fi < fti.count; fi++) { + struct ForestTrustInfoRecord *rec = + &fti.records[fi].record; + struct ForestTrustDataDomainInfo *drec = NULL; + + if (rec->type != FOREST_TRUST_DOMAIN_INFO) { + continue; + } + drec = &rec->data.info; + + if (rec->flags & LSA_NB_DISABLED_MASK) { + continue; + } + + if (rec->flags & LSA_SID_DISABLED_MASK) { + continue; + } + + /* + * TODO: + * also try to find a matching + * LSA_TLN_DISABLED_MASK ??? + */ + + domain = find_domain_from_name_noinit(drec->netbios_name.string); + if (domain != NULL) { + continue; + } + + status = add_trusted_domain(drec->netbios_name.string, + drec->dns_name.string, + &drec->sid, + LSA_TRUST_TYPE_UPLEVEL, + NETR_TRUST_FLAG_OUTBOUND, + 0, + SEC_CHAN_NULL, + routing_domain, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("add_trusted_domain returned %s\n", + nt_errstr(status)); + return false; + } + if (domain == NULL) { + continue; + } + } + } + + return true; +} + + /* Look up global info for the winbind daemon */ bool init_domain_list(void) { - struct winbindd_domain *domain; int role = lp_server_role(); + struct pdb_domain_info *pdb_domain_info = NULL; + struct winbindd_domain *domain = NULL; + NTSTATUS status; + bool ok; /* Free existing list */ free_domain_list(); /* BUILTIN domain */ - domain = add_trusted_domain("BUILTIN", NULL, &cache_methods, - &global_sid_Builtin); - if (domain) { - setup_domain_child(domain); + status = add_trusted_domain("BUILTIN", + NULL, + &global_sid_Builtin, + LSA_TRUST_TYPE_DOWNLEVEL, + 0, /* trust_flags */ + 0, /* trust_attribs */ + SEC_CHAN_LOCAL, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("add_trusted_domain BUILTIN returned %s\n", + nt_errstr(status)); + return false; } /* Local SAM */ - domain = add_trusted_domain(get_global_sam_name(), NULL, - &cache_methods, get_global_sam_sid()); - if (domain) { - if ( role != ROLE_DOMAIN_MEMBER ) { - domain->primary = True; + /* + * In case the passdb backend is passdb_dsdb the domain SID comes from + * dsdb, not from secrets.tdb. As we use the domain SID in various + * places, we must ensure the domain SID is migrated from dsdb to + * secrets.tdb before get_global_sam_sid() is called the first time. + * + * The migration is done as part of the passdb_dsdb initialisation, + * calling pdb_get_domain_info() triggers it. + */ + pdb_domain_info = pdb_get_domain_info(talloc_tos()); + + if ( role == ROLE_ACTIVE_DIRECTORY_DC ) { + uint32_t trust_flags; + bool is_root; + enum netr_SchannelType sec_chan_type; + const char *account_name; + struct samr_Password current_nt_hash; + + if (pdb_domain_info == NULL) { + DEBUG(0, ("Failed to fetch our own, local AD " + "domain info from sam.ldb\n")); + return false; + } + + trust_flags = NETR_TRUST_FLAG_PRIMARY; + trust_flags |= NETR_TRUST_FLAG_IN_FOREST; + trust_flags |= NETR_TRUST_FLAG_NATIVE; + trust_flags |= NETR_TRUST_FLAG_OUTBOUND; + + is_root = strequal(pdb_domain_info->dns_domain, + pdb_domain_info->dns_forest); + if (is_root) { + trust_flags |= NETR_TRUST_FLAG_TREEROOT; + } + + status = add_trusted_domain(pdb_domain_info->name, + pdb_domain_info->dns_domain, + &pdb_domain_info->sid, + LSA_TRUST_TYPE_UPLEVEL, + trust_flags, + LSA_TRUST_ATTRIBUTE_WITHIN_FOREST, + SEC_CHAN_BDC, + NULL, + &domain); + TALLOC_FREE(pdb_domain_info); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to add our own, local AD " + "domain to winbindd's internal list\n"); + return false; + } + + /* + * We need to call this to find out if we are an RODC + */ + ok = get_trust_pw_hash(domain->name, + current_nt_hash.hash, + &account_name, + &sec_chan_type); + if (!ok) { + /* + * If get_trust_pw_hash() fails, then try and + * fetch the password from the more recent of + * secrets.{ldb,tdb} using the + * pdb_get_trust_credentials() + */ + ok = migrate_secrets_tdb_to_ldb(domain); + + if (!ok) { + DEBUG(0, ("Failed to migrate our own, " + "local AD domain join password for " + "winbindd's internal use into " + "secrets.tdb\n")); + return false; + } + ok = get_trust_pw_hash(domain->name, + current_nt_hash.hash, + &account_name, + &sec_chan_type); + if (!ok) { + DEBUG(0, ("Failed to find our our own, just " + "written local AD domain join " + "password for winbindd's internal " + "use in secrets.tdb\n")); + return false; + } + } + + domain->secure_channel_type = sec_chan_type; + if (sec_chan_type == SEC_CHAN_RODC) { + domain->rodc = true; + } + + } else { + uint32_t trust_flags; + enum netr_SchannelType secure_channel_type; + + trust_flags = NETR_TRUST_FLAG_OUTBOUND; + if (role != ROLE_DOMAIN_MEMBER) { + trust_flags |= NETR_TRUST_FLAG_PRIMARY; + } + + if (role > ROLE_DOMAIN_MEMBER) { + secure_channel_type = SEC_CHAN_BDC; + } else { + secure_channel_type = SEC_CHAN_LOCAL; + } + + status = add_trusted_domain(get_global_sam_name(), + NULL, + get_global_sam_sid(), + LSA_TRUST_TYPE_DOWNLEVEL, + trust_flags, + 0, /* trust_attribs */ + secure_channel_type, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to add local SAM to " + "domain to winbindd's internal list\n"); + return false; } - setup_domain_child(domain); } - /* Add ourselves as the first entry. */ + if (IS_DC) { + ok = add_trusted_domains_dc(); + if (!ok) { + DBG_ERR("init_domain_list_dc failed\n"); + return false; + } + } if ( role == ROLE_DOMAIN_MEMBER ) { struct dom_sid our_sid; + uint32_t trust_type; if (!secrets_fetch_domain_sid(lp_workgroup(), &our_sid)) { DEBUG(0, ("Could not fetch our SID - did we join?\n")); return False; } - domain = add_trusted_domain( lp_workgroup(), lp_realm(), - &cache_methods, &our_sid); - if (domain) { - domain->primary = True; - setup_domain_child(domain); + if (lp_realm() != NULL) { + trust_type = LSA_TRUST_TYPE_UPLEVEL; + } else { + trust_type = LSA_TRUST_TYPE_DOWNLEVEL; + } - /* Even in the parent winbindd we'll need to - talk to the DC, so try and see if we can - contact it. Theoretically this isn't neccessary - as the init_dc_connection() in init_child_recv() - will do this, but we can start detecting the DC - early here. */ - set_domain_online_request(domain); + status = add_trusted_domain(lp_workgroup(), + lp_realm(), + &our_sid, + trust_type, + NETR_TRUST_FLAG_PRIMARY| + NETR_TRUST_FLAG_OUTBOUND, + 0, /* trust_attribs */ + SEC_CHAN_WKSTA, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to add local SAM to " + "domain to winbindd's internal list\n"); + return false; } + /* Even in the parent winbindd we'll need to + talk to the DC, so try and see if we can + contact it. Theoretically this isn't neccessary + as the init_dc_connection() in init_child_recv() + will do this, but we can start detecting the DC + early here. */ + set_domain_online_request(domain); + + } + + status = imessaging_register(winbind_imessaging_context(), NULL, + MSG_WINBIND_RELOAD_TRUSTED_DOMAINS, + wb_imsg_new_trusted_domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("imessaging_register failed %s\n", nt_errstr(status)); + return false; } return True; @@ -682,9 +1315,13 @@ struct winbindd_domain *find_domain_from_name_noinit(const char *domain_name) /* Search through list */ for (domain = domain_list(); domain != NULL; domain = domain->next) { - if (strequal(domain_name, domain->name) || - (domain->alt_name != NULL && - strequal(domain_name, domain->alt_name))) { + if (strequal(domain_name, domain->name)) { + return domain; + } + if (domain->alt_name == NULL) { + continue; + } + if (strequal(domain_name, domain->alt_name)) { return domain; } } @@ -694,6 +1331,28 @@ struct winbindd_domain *find_domain_from_name_noinit(const char *domain_name) return NULL; } +/** + * Given a domain name, return the struct winbindd domain if it's a direct + * outgoing trust + * + * @return The domain structure for the named domain, if it is a direct outgoing trust + */ +struct winbindd_domain *find_trust_from_name_noinit(const char *domain_name) +{ + struct winbindd_domain *domain = NULL; + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return NULL; +} + struct winbindd_domain *find_domain_from_name(const char *domain_name) { struct winbindd_domain *domain; @@ -704,7 +1363,7 @@ struct winbindd_domain *find_domain_from_name(const char *domain_name) return NULL; if (!domain->initialized) - init_dc_connection(domain); + init_dc_connection(domain, false); return domain; } @@ -727,6 +1386,28 @@ struct winbindd_domain *find_domain_from_sid_noinit(const struct dom_sid *sid) return NULL; } +/** + * Given a domain sid, return the struct winbindd domain if it's a direct + * outgoing trust + * + * @return The domain structure for the specified domain, if it is a direct outgoing trust + */ +struct winbindd_domain *find_trust_from_sid_noinit(const struct dom_sid *sid) +{ + struct winbindd_domain *domain = NULL; + + domain = find_domain_from_sid_noinit(sid); + if (domain == NULL) { + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return NULL; +} + /* Given a domain sid, return the struct winbindd domain info for it */ struct winbindd_domain *find_domain_from_sid(const struct dom_sid *sid) @@ -739,7 +1420,7 @@ struct winbindd_domain *find_domain_from_sid(const struct dom_sid *sid) return NULL; if (!domain->initialized) - init_dc_connection(domain); + init_dc_connection(domain, false); return domain; } @@ -759,54 +1440,61 @@ struct winbindd_domain *find_our_domain(void) return NULL; } -struct winbindd_domain *find_root_domain(void) +struct winbindd_domain *find_default_route_domain(void) { - struct winbindd_domain *ours = find_our_domain(); - - if (ours->forest_name == NULL) { - return NULL; - } - - return find_domain_from_name( ours->forest_name ); -} - -struct winbindd_domain *find_builtin_domain(void) -{ - struct winbindd_domain *domain; - - domain = find_domain_from_sid(&global_sid_Builtin); - if (domain == NULL) { - smb_panic("Could not find BUILTIN domain"); + if (!IS_DC) { + return find_our_domain(); } - - return domain; + DBG_DEBUG("Routing logic not yet implemented on a DC\n"); + return NULL; } /* Find the appropriate domain to lookup a name or SID */ struct winbindd_domain *find_lookup_domain_from_sid(const struct dom_sid *sid) { - /* SIDs in the S-1-22-{1,2} domain should be handled by our passdb */ + DBG_DEBUG("SID [%s]\n", sid_string_dbg(sid)); + + /* + * SIDs in the S-1-22-{1,2} domain and well-known SIDs should be handled + * by our passdb. + */ if ( sid_check_is_in_unix_groups(sid) || sid_check_is_unix_groups(sid) || sid_check_is_in_unix_users(sid) || - sid_check_is_unix_users(sid) ) + sid_check_is_unix_users(sid) || + sid_check_is_wellknown_domain(sid, NULL) || + sid_check_is_in_wellknown_domain(sid) ) { return find_domain_from_sid(get_global_sam_sid()); } - /* A DC can't ask the local smbd for remote SIDs, here winbindd is the - * one to contact the external DC's. On member servers the internal - * domains are different: These are part of the local SAM. */ - - DEBUG(10, ("find_lookup_domain_from_sid(%s)\n", sid_string_dbg(sid))); + /* + * On member servers the internal domains are different: These are part + * of the local SAM. + */ - if (IS_DC || is_internal_domain(sid) || is_in_internal_domain(sid)) { + if (is_internal_domain(sid) || is_in_internal_domain(sid)) { DEBUG(10, ("calling find_domain_from_sid\n")); return find_domain_from_sid(sid); } + if (IS_DC) { + struct winbindd_domain *domain = NULL; + + domain = find_domain_from_sid_noinit(sid); + if (domain == NULL) { + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return domain->routing_domain; + } + /* On a member server a query for SID or name can always go to our * primary DC. */ @@ -826,10 +1514,24 @@ struct winbindd_domain *find_lookup_domain_from_name(const char *domain_name) return find_domain_from_name_noinit( get_global_sam_name() ); } - if (IS_DC || strequal(domain_name, "BUILTIN") || + if (strequal(domain_name, "BUILTIN") || strequal(domain_name, get_global_sam_name())) return find_domain_from_name_noinit(domain_name); + if (IS_DC) { + struct winbindd_domain *domain = NULL; + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return domain->routing_domain; + } return find_our_domain(); } @@ -849,7 +1551,7 @@ static bool assume_domain(const char *domain) if ( !strequal(lp_workgroup(), domain) ) return False; - if ( lp_winbind_use_default_domain() || lp_winbind_trusted_domains_only() ) + if ( lp_winbind_use_default_domain() ) return True; } @@ -870,10 +1572,11 @@ bool parse_domain_user(const char *domuser, fstring domain, fstring user) if ( !p ) { fstrcpy(user, domuser); + p = strchr(domuser, '@'); - if ( assume_domain(lp_workgroup())) { + if ( assume_domain(lp_workgroup()) && p == NULL) { fstrcpy(domain, lp_workgroup()); - } else if ((p = strchr(domuser, '@')) != NULL) { + } else if (p != NULL) { fstrcpy(domain, p + 1); user[PTR_DIFF(p, domuser)] = 0; } else { @@ -929,8 +1632,7 @@ bool canonicalize_username(fstring username_inout, fstring domain, fstring user) If we are a PDC or BDC, and this is for our domain, do likewise. - Also, if omit DOMAIN if 'winbind trusted domains only = true', as the - username is then unqualified in unix + On an AD DC we always fill DOMAIN\\USERNAME. We always canonicalize as UPPERCASE DOMAIN, lowercase username. */ @@ -938,6 +1640,10 @@ void fill_domain_username(fstring name, const char *domain, const char *user, bo { fstring tmp_user; + if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + can_assume = false; + } + fstrcpy(tmp_user, user); (void)strlower_m(tmp_user); @@ -961,6 +1667,10 @@ char *fill_domain_username_talloc(TALLOC_CTX *mem_ctx, { char *tmp_user, *name; + if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + can_assume = false; + } + tmp_user = talloc_strdup(mem_ctx, user); if (!strlower_m(tmp_user)) { TALLOC_FREE(tmp_user); @@ -994,10 +1704,26 @@ struct winbindd_cli_state *winbindd_client_list(void) return _client_list; } +/* Return list-tail of all connected clients */ + +struct winbindd_cli_state *winbindd_client_list_tail(void) +{ + return DLIST_TAIL(_client_list); +} + +/* Return previous (read:newer) client in list */ + +struct winbindd_cli_state * +winbindd_client_list_prev(struct winbindd_cli_state *cli) +{ + return DLIST_PREV(cli); +} + /* Add a connection to the list */ void winbindd_add_client(struct winbindd_cli_state *cli) { + cli->last_access = time(NULL); DLIST_ADD(_client_list, cli); _num_clients++; } @@ -1010,6 +1736,14 @@ void winbindd_remove_client(struct winbindd_cli_state *cli) _num_clients--; } +/* Move a client to head or list */ + +void winbindd_promote_client(struct winbindd_cli_state *cli) +{ + cli->last_access = time(NULL); + DLIST_PROMOTE(_client_list, cli); +} + /* Return number of open clients */ int winbindd_num_clients(void) @@ -1017,8 +1751,7 @@ int winbindd_num_clients(void) return _num_clients; } -NTSTATUS lookup_usergroups_cached(struct winbindd_domain *domain, - TALLOC_CTX *mem_ctx, +NTSTATUS lookup_usergroups_cached(TALLOC_CTX *mem_ctx, const struct dom_sid *user_sid, uint32_t *p_num_groups, struct dom_sid **user_sids) { @@ -1037,11 +1770,6 @@ NTSTATUS lookup_usergroups_cached(struct winbindd_domain *domain, return NT_STATUS_OBJECT_NAME_NOT_FOUND; } - if (info3->base.groups.count == 0) { - TALLOC_FREE(info3); - return NT_STATUS_UNSUCCESSFUL; - } - /* * Before bug #7843 the "Domain Local" groups were added with a * lookupuseraliases call, but this isn't done anymore for our domain @@ -1074,10 +1802,11 @@ NTSTATUS lookup_usergroups_cached(struct winbindd_domain *domain, ********************************************************************/ NTSTATUS normalize_name_map(TALLOC_CTX *mem_ctx, - struct winbindd_domain *domain, + const char *domain_name, const char *name, char **normalized) { + struct winbindd_domain *domain = NULL; NTSTATUS nt_status; if (!name || !normalized) { @@ -1088,6 +1817,12 @@ NTSTATUS normalize_name_map(TALLOC_CTX *mem_ctx, return NT_STATUS_PROCEDURE_NOT_FOUND; } + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + DBG_ERR("Failed to find domain '%s'\n", domain_name); + return NT_STATUS_NO_SUCH_DOMAIN; + } + /* Alias support and whitespace replacement are mutually exclusive */ @@ -1354,9 +2089,6 @@ void set_auth_errors(struct winbindd_response *resp, NTSTATUS result) bool is_domain_offline(const struct winbindd_domain *domain) { - if (!lp_winbind_offline_logon()) { - return false; - } if (get_global_winbindd_state_offline()) { return true; } @@ -1394,7 +2126,7 @@ bool parse_sidlist(TALLOC_CTX *mem_ctx, const char *sidstr, DEBUG(1, ("Could not parse sid %s\n", p)); return false; } - if ((q == NULL) || (q[0] != '\n')) { + if (q[0] != '\n') { DEBUG(1, ("Got invalid sidstr: %s\n", p)); return false; } @@ -1407,3 +2139,67 @@ bool parse_sidlist(TALLOC_CTX *mem_ctx, const char *sidstr, } return True; } + +bool parse_xidlist(TALLOC_CTX *mem_ctx, const char *xidstr, + struct unixid **pxids, uint32_t *pnum_xids) +{ + const char *p; + struct unixid *xids = NULL; + uint32_t num_xids = 0; + + p = xidstr; + if (p == NULL) { + return false; + } + + while (p[0] != '\0') { + struct unixid *tmp; + struct unixid xid; + unsigned long long id; + char *endp; + + switch (p[0]) { + case 'U': + xid = (struct unixid) { .type = ID_TYPE_UID }; + break; + case 'G': + xid = (struct unixid) { .type = ID_TYPE_GID }; + break; + default: + return false; + } + + p += 1; + + id = strtoull(p, &endp, 10); + if ((id == ULLONG_MAX) && (errno == ERANGE)) { + goto fail; + } + if (*endp != '\n') { + goto fail; + } + p = endp+1; + + xid.id = id; + if ((unsigned long long)xid.id != id) { + goto fail; + } + + tmp = talloc_realloc(mem_ctx, xids, struct unixid, num_xids+1); + if (tmp == NULL) { + return 0; + } + xids = tmp; + + xids[num_xids] = xid; + num_xids += 1; + } + + *pxids = xids; + *pnum_xids = num_xids; + return true; + +fail: + TALLOC_FREE(xids); + return false; +}