diff options
Diffstat (limited to 'source3/winbindd')
29 files changed, 5545 insertions, 143 deletions
diff --git a/source3/winbindd/idmap_ad.c b/source3/winbindd/idmap_ad.c index 9fefb1bba7..60a2d8642a 100644 --- a/source3/winbindd/idmap_ad.c +++ b/source3/winbindd/idmap_ad.c @@ -517,6 +517,8 @@ again: bidx = idx; for (i = 0; (i < IDMAP_AD_MAX_IDS) && ids[idx]; i++, idx++) { + ids[idx]->status = ID_UNKNOWN; + sidstr = sid_binstring(ids[idx]->sid); filter = talloc_asprintf_append_buffer(filter, "(objectSid=%s)", sidstr); @@ -732,6 +734,16 @@ static NTSTATUS nss_ad_get_info( struct nss_domain_entry *e, uint32 *gid ) { ADS_STRUCT *ads_internal = NULL; + const char *attrs[] = {NULL, /* attr_homedir */ + NULL, /* attr_shell */ + NULL, /* attr_gecos */ + NULL, /* attr_gidnumber */ + NULL }; + char *filter = NULL; + LDAPMessage *msg_internal = NULL; + ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + char *sidstr = NULL; /* Only do query if we are online */ if (idmap_is_offline()) { @@ -743,24 +755,224 @@ static NTSTATUS nss_ad_get_info( struct nss_domain_entry *e, ads_internal = ad_idmap_cached_connection(); - if ( !ads_internal || !ad_schema ) + if ( !ads_internal || !ad_schema ) { return NT_STATUS_OBJECT_NAME_NOT_FOUND; - - if ( !homedir || !shell || !gecos ) + } + + if (!sid || !homedir || !shell || !gecos) { return NT_STATUS_INVALID_PARAMETER; + } + + /* See if we can use the ADS connection struct swe were given */ - *homedir = ads_pull_string( ads, ctx, msg, ad_schema->posix_homedir_attr ); - *shell = ads_pull_string( ads, ctx, msg, ad_schema->posix_shell_attr ); - *gecos = ads_pull_string( ads, ctx, msg, ad_schema->posix_gecos_attr ); - - if ( gid ) { - if ( !ads_pull_uint32(ads, msg, ad_schema->posix_gidnumber_attr, gid ) ) - *gid = (uint32)-1; + if (ads) { + *homedir = ads_pull_string( ads, ctx, msg, ad_schema->posix_homedir_attr ); + *shell = ads_pull_string( ads, ctx, msg, ad_schema->posix_shell_attr ); + *gecos = ads_pull_string( ads, ctx, msg, ad_schema->posix_gecos_attr ); + + if (gid) { + if ( !ads_pull_uint32(ads, msg, ad_schema->posix_gidnumber_attr, gid ) ) + *gid = (uint32)-1; + } + + nt_status = NT_STATUS_OK; + goto done; } - - return NT_STATUS_OK; + + /* Have to do our own query */ + + attrs[0] = ad_schema->posix_homedir_attr; + attrs[1] = ad_schema->posix_shell_attr; + attrs[2] = ad_schema->posix_gecos_attr; + attrs[3] = ad_schema->posix_gidnumber_attr; + + sidstr = sid_binstring(sid); + filter = talloc_asprintf(ctx, "(objectSid=%s)", sidstr); + SAFE_FREE(sidstr); + + if (!filter) { + nt_status = NT_STATUS_NO_MEMORY; + goto done; + } + + ads_status = ads_search_retry(ads_internal, &msg_internal, filter, attrs); + if (!ADS_ERR_OK(ads_status)) { + nt_status = ads_ntstatus(ads_status); + goto done; + } + + *homedir = ads_pull_string(ads_internal, ctx, msg_internal, ad_schema->posix_homedir_attr); + *shell = ads_pull_string(ads_internal, ctx, msg_internal, ad_schema->posix_shell_attr); + *gecos = ads_pull_string(ads_internal, ctx, msg_internal, ad_schema->posix_gecos_attr); + + if (gid) { + if (!ads_pull_uint32(ads_internal, msg_internal, ad_schema->posix_gidnumber_attr, gid)) + *gid = (uint32)-1; + } + + nt_status = NT_STATUS_OK; + +done: + if (msg_internal) { + ads_msgfree(ads_internal, msg_internal); + } + + return nt_status; } +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_ad_map_to_alias(TALLOC_CTX *mem_ctx, + const char *domain, + const char *name, + char **alias) +{ + ADS_STRUCT *ads_internal = NULL; + const char *attrs[] = {NULL, /* attr_uid */ + NULL }; + char *filter = NULL; + LDAPMessage *msg = NULL; + ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + /* Check incoming parameters */ + + if ( !domain || !name || !*alias) { + nt_status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + /* Only do query if we are online */ + + if (idmap_is_offline()) { + nt_status = NT_STATUS_FILE_IS_OFFLINE; + goto done; + } + + ads_internal = ad_idmap_cached_connection(); + + if (!ads_internal || !ad_schema) { + nt_status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + goto done; + } + + attrs[0] = ad_schema->posix_uid_attr; + + filter = talloc_asprintf(mem_ctx, + "(sAMAccountName=%s)", + name); + if (!filter) { + nt_status = NT_STATUS_NO_MEMORY; + goto done; + } + + ads_status = ads_search_retry(ads_internal, &msg, filter, attrs); + if (!ADS_ERR_OK(ads_status)) { + nt_status = ads_ntstatus(ads_status); + goto done; + } + + *alias = ads_pull_string(ads_internal, mem_ctx, msg, ad_schema->posix_uid_attr ); + + if (!*alias) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + nt_status = NT_STATUS_OK; + +done: + if (filter) { + talloc_destroy(filter); + } + if (msg) { + ads_msgfree(ads_internal, msg); + } + + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_ad_map_from_alias( TALLOC_CTX *mem_ctx, + const char *domain, + const char *alias, + char **name ) +{ + ADS_STRUCT *ads_internal = NULL; + const char *attrs[] = {"sAMAccountName", + NULL }; + char *filter = NULL; + LDAPMessage *msg = NULL; + ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + char *username; + + /* Check incoming parameters */ + + if ( !alias || !name) { + nt_status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + /* Only do query if we are online */ + + if (idmap_is_offline()) { + nt_status = NT_STATUS_FILE_IS_OFFLINE; + goto done; + } + + ads_internal = ad_idmap_cached_connection(); + + if (!ads_internal || !ad_schema) { + nt_status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + goto done; + } + + filter = talloc_asprintf(mem_ctx, + "(%s=%s)", + ad_schema->posix_uid_attr, + alias); + if (!filter) { + nt_status = NT_STATUS_NO_MEMORY; + goto done; + } + + ads_status = ads_search_retry(ads_internal, &msg, filter, attrs); + if (!ADS_ERR_OK(ads_status)) { + nt_status = ads_ntstatus(ads_status); + goto done; + } + + username = ads_pull_string(ads_internal, mem_ctx, msg, + "sAMAccountName"); + if (!username) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + *name = talloc_asprintf(mem_ctx, "%s\\%s", + lp_workgroup(), + username); + if (!*name) { + nt_status = NT_STATUS_NO_MEMORY; + goto done; + } + + nt_status = NT_STATUS_OK; + +done: + if (filter) { + talloc_destroy(filter); + } + if (msg) { + ads_msgfree(ads_internal, msg); + } + + return nt_status; +} + + /************************************************************************ ***********************************************************************/ @@ -786,21 +998,27 @@ static struct idmap_methods ad_methods = { function which sets the intended schema model to use */ static struct nss_info_methods nss_rfc2307_methods = { - .init = nss_rfc2307_init, - .get_nss_info = nss_ad_get_info, - .close_fn = nss_ad_close + .init = nss_rfc2307_init, + .get_nss_info = nss_ad_get_info, + .map_to_alias = nss_ad_map_to_alias, + .map_from_alias = nss_ad_map_from_alias, + .close_fn = nss_ad_close }; static struct nss_info_methods nss_sfu_methods = { - .init = nss_sfu_init, - .get_nss_info = nss_ad_get_info, - .close_fn = nss_ad_close + .init = nss_sfu_init, + .get_nss_info = nss_ad_get_info, + .map_to_alias = nss_ad_map_to_alias, + .map_from_alias = nss_ad_map_from_alias, + .close_fn = nss_ad_close }; static struct nss_info_methods nss_sfu20_methods = { - .init = nss_sfu20_init, - .get_nss_info = nss_ad_get_info, - .close_fn = nss_ad_close + .init = nss_sfu20_init, + .get_nss_info = nss_ad_get_info, + .map_to_alias = nss_ad_map_to_alias, + .map_from_alias = nss_ad_map_from_alias, + .close_fn = nss_ad_close }; diff --git a/source3/winbindd/idmap_adex/cell_util.c b/source3/winbindd/idmap_adex/cell_util.c new file mode 100644 index 0000000000..f5c08a0454 --- /dev/null +++ b/source3/winbindd/idmap_adex/cell_util.c @@ -0,0 +1,292 @@ +/* + * idmap_adex: Support for AD Forests + * + * Copyright (C) Gerald (Jerry) Carter 2006-2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "idmap_adex.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +/********************************************************************** +**********************************************************************/ + + char *find_attr_string(char **list, size_t num_lines, const char *substr) +{ + int i; + int cmplen = strlen(substr); + + for (i = 0; i < num_lines; i++) { + /* make sure to avoid substring matches like uid + and uidNumber */ + if ((StrnCaseCmp(list[i], substr, cmplen) == 0) && + (list[i][cmplen] == '=')) { + /* Don't return an empty string */ + if (list[i][cmplen + 1] != '\0') + return &(list[i][cmplen + 1]); + + return NULL; + } + } + + return NULL; +} + +/********************************************************************** +**********************************************************************/ + + bool is_object_class(char **list, size_t num_lines, const char *substr) +{ + int i; + + for (i = 0; i < num_lines; i++) { + if (strequal(list[i], substr)) { + return true; + } + } + + return false; +} + +/********************************************************************** + Find out about the cell (e.g. use2307Attrs, etc...) +**********************************************************************/ + + NTSTATUS cell_lookup_settings(struct likewise_cell * cell) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + /* Parameter check */ + + if (!cell) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Only supporting Forest-wide, schema based searches */ + + cell_set_flags(cell, LWCELL_FLAG_USE_RFC2307_ATTRS); + cell_set_flags(cell, LWCELL_FLAG_SEARCH_FOREST); + + cell->provider = &ccp_unified; + + nt_status = NT_STATUS_OK; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1,("LWI: Failed to obtain cell settings (%s)\n", + nt_errstr(nt_status))); + } + + return nt_status; +} + + +static NTSTATUS cell_lookup_forest(struct likewise_cell *c) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct gc_info *gc = NULL; + + if (!c) { + return NT_STATUS_INVALID_PARAMETER; + } + + if ((gc = TALLOC_ZERO_P(NULL, struct gc_info)) == NULL) { + nt_status = NT_STATUS_NO_MEMORY; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Query the rootDSE for the forest root naming conect first. + Check that the a GC server for the forest has not already + been added */ + + nt_status = gc_find_forest_root(gc, cell_dns_domain(c)); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + c->forest_name = talloc_strdup(c, gc->forest_name); + BAIL_ON_PTR_ERROR(c->forest_name, nt_status); + +done: + if (gc) { + talloc_free(gc); + } + + return nt_status; +} + +/********************************************************************** +**********************************************************************/ + + NTSTATUS cell_locate_membership(ADS_STRUCT * ads) +{ + ADS_STATUS status; + char *domain_dn = ads_build_dn(lp_realm()); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + DOM_SID sid; + struct likewise_cell *cell = NULL; + + /* In the Likewise plugin, I had to support the concept of cells + based on the machine's membership in an OU. However, now I'll + just assume our membership in the forest cell */ + + DEBUG(2, ("locate_cell_membership: Located membership " + "in cell \"%s\"\n", domain_dn)); + + if ((cell = cell_new()) == NULL) { + nt_status = NT_STATUS_NO_MEMORY; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + status = ads_domain_sid(ads, &sid); + if (!ADS_ERR_OK(status)) { + DEBUG(3,("locate_cell_membership: Failed to find " + "domain SID for %s\n", domain_dn)); + } + + /* save the SID and search base for our domain */ + + cell_set_dns_domain(cell, lp_realm()); + cell_set_connection(cell, ads); + cell_set_dn(cell, domain_dn); + cell_set_domain_sid(cell, &sid); + + /* Now save our forest root */ + + cell_lookup_forest(cell); + + /* Add the cell to the list */ + + if (!cell_list_add(cell)) { + nt_status = NT_STATUS_INSUFFICIENT_RESOURCES; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Done! */ + nt_status = NT_STATUS_OK; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0,("LWI: Failed to locate cell membership (%s)\n", + nt_errstr(nt_status))); + } + + SAFE_FREE(domain_dn); + + return nt_status; +} + +/********************************************************************* + ********************************************************************/ + + int min_id_value(void) +{ + int id_val; + + id_val = lp_parm_int(-1, "lwidentity", "min_id_value", MIN_ID_VALUE); + + /* Still don't let it go below 50 */ + + return MAX(50, id_val); +} + +/******************************************************************** + *******************************************************************/ + + char *cell_dn_to_dns(const char *dn) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + char *domain = NULL; + char *dns_name = NULL; + const char *tmp_dn; + char *buffer = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!dn || !*dn) { + goto done; + } + + tmp_dn = talloc_strdup(frame, dn); + BAIL_ON_PTR_ERROR(tmp_dn, nt_status); + + while (next_token_talloc(frame, &tmp_dn, &buffer, ",")) { + + /* skip everything up the where DC=... begins */ + if (StrnCaseCmp(buffer, "DC=", 3) != 0) + continue; + + if (!domain) { + domain = talloc_strdup(frame, &buffer[3]); + } else { + domain = talloc_asprintf_append(domain, ".%s", + &buffer[3]); + } + BAIL_ON_PTR_ERROR(domain, nt_status); + } + + dns_name = SMB_STRDUP(domain); + BAIL_ON_PTR_ERROR(dns_name, nt_status); + + nt_status = NT_STATUS_OK; + +done: + PRINT_NTSTATUS_ERROR(nt_status, "cell_dn_to_dns", 1); + + talloc_destroy(frame); + + return dns_name; +} + +/********************************************************************* + ********************************************************************/ + + NTSTATUS get_sid_type(ADS_STRUCT *ads, + LDAPMessage *msg, + enum lsa_SidType *type) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + uint32_t atype; + + if (!ads_pull_uint32(ads, msg, "sAMAccountType", &atype)) { + nt_status = NT_STATUS_INVALID_USER_BUFFER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + switch (atype &0xF0000000) { + case ATYPE_SECURITY_GLOBAL_GROUP: + *type = SID_NAME_DOM_GRP; + break; + case ATYPE_SECURITY_LOCAL_GROUP: + *type = SID_NAME_ALIAS; + break; + case ATYPE_NORMAL_ACCOUNT: + case ATYPE_WORKSTATION_TRUST: + case ATYPE_INTERDOMAIN_TRUST: + *type = SID_NAME_USER; + break; + default: + *type = SID_NAME_USE_NONE; + nt_status = NT_STATUS_INVALID_ACCOUNT_NAME; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + nt_status = NT_STATUS_OK; + +done: + return nt_status; +} diff --git a/source3/winbindd/idmap_adex/domain_util.c b/source3/winbindd/idmap_adex/domain_util.c new file mode 100644 index 0000000000..6851503cc8 --- /dev/null +++ b/source3/winbindd/idmap_adex/domain_util.c @@ -0,0 +1,286 @@ +/* + * idmap_adex: Domain search interface + * + * Copyright (C) Gerald (Jerry) Carter 2007-2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "idmap_adex.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +struct dc_info { + struct dc_info *prev, *next; + char *dns_name; + struct likewise_cell *domain_cell; +}; + +static struct dc_info *_dc_server_list = NULL; + + +/********************************************************************** + *********************************************************************/ + +static struct dc_info *dc_list_head(void) +{ + return _dc_server_list; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS dc_add_domain(const char *domain) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct dc_info *dc = NULL; + + if (!domain) { + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(10,("dc_add_domain: Attempting to add domain %s\n", domain)); + + /* Check for duplicates */ + + dc = dc_list_head(); + while (dc) { + if (strequal (dc->dns_name, domain)) + break; + dc = dc->next; + } + + if (dc) { + DEBUG(10,("dc_add_domain: %s already in list\n", domain)); + return NT_STATUS_OK; + } + + dc = TALLOC_ZERO_P(NULL, struct dc_info); + BAIL_ON_PTR_ERROR(dc, nt_status); + + dc->dns_name = talloc_strdup(dc, domain); + BAIL_ON_PTR_ERROR(dc->dns_name, nt_status); + + DLIST_ADD_END(_dc_server_list, dc, struct dc_info*); + + nt_status = NT_STATUS_OK; + + DEBUG(5,("dc_add_domain: Successfully added %s\n", domain)); + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_destroy(dc); + DEBUG(0,("LWI: Failed to add new DC connection for %s (%s)\n", + domain, nt_errstr(nt_status))); + } + + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static void dc_server_list_destroy(void) +{ + struct dc_info *dc = dc_list_head(); + + while (dc) { + struct dc_info *p = dc->next; + + cell_destroy(dc->domain_cell); + talloc_destroy(dc); + + dc = p; + } + + return; +} + + +/********************************************************************** + *********************************************************************/ + + NTSTATUS domain_init_list(void) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct winbindd_tdc_domain *domains = NULL; + size_t num_domains = 0; + int i; + + if (_dc_server_list != NULL) { + dc_server_list_destroy(); + } + + /* Add our domain */ + + nt_status = dc_add_domain(lp_realm()); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if (!wcache_tdc_fetch_list(&domains, &num_domains)) { + nt_status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Add all domains with an incoming trust path */ + + for (i=0; i<num_domains; i++) { + uint32_t flags = (NETR_TRUST_FLAG_INBOUND|NETR_TRUST_FLAG_IN_FOREST); + + /* We just require one of the flags to be set here */ + + if (domains[i].trust_flags & flags) { + nt_status = dc_add_domain(domains[i].dns_name); + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + } + + nt_status = NT_STATUS_OK; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(2,("LWI: Failed to initialize DC list (%s)\n", + nt_errstr(nt_status))); + } + + TALLOC_FREE(domains); + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS dc_do_search(struct dc_info *dc, + const char *search_base, + int scope, + const char *expr, + const char **attrs, + LDAPMessage ** msg) +{ + ADS_STATUS status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + status = cell_do_search(dc->domain_cell, search_base, + scope, expr, attrs, msg); + nt_status = ads_ntstatus(status); + + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static struct dc_info *dc_find_domain(const char *dns_domain) +{ + struct dc_info *dc = dc_list_head(); + + if (!dc) + return NULL; + + while (dc) { + if (strequal(dc->dns_name, dns_domain)) { + return dc; + } + + dc = dc->next; + } + + return NULL; +} + +/********************************************************************** + *********************************************************************/ + + NTSTATUS dc_search_domains(struct likewise_cell **cell, + LDAPMessage **msg, + const char *dn, + const DOM_SID *sid) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *frame = talloc_stackframe(); + char *dns_domain; + const char *attrs[] = { "*", NULL }; + struct dc_info *dc = NULL; + const char *base = NULL; + + if (!dn || !*dn) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + dns_domain = cell_dn_to_dns(dn); + BAIL_ON_PTR_ERROR(dns_domain, nt_status); + + if ((dc = dc_find_domain(dns_domain)) == NULL) { + nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Reparse the cell settings for the domain if necessary */ + + if (!dc->domain_cell) { + char *base_dn; + + base_dn = ads_build_dn(dc->dns_name); + BAIL_ON_PTR_ERROR(base_dn, nt_status); + + nt_status = cell_connect_dn(&dc->domain_cell, base_dn); + SAFE_FREE(base_dn); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = cell_lookup_settings(dc->domain_cell); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* By definition this is already part of a larger + forest-wide search scope */ + + cell_set_flags(dc->domain_cell, LWCELL_FLAG_SEARCH_FOREST); + } + + /* Check whether we are operating in non-schema or RFC2307 + mode */ + + if (cell_flags(dc->domain_cell) & LWCELL_FLAG_USE_RFC2307_ATTRS) { + nt_status = dc_do_search(dc, dn, LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, msg); + } else { + const char *sid_str = NULL; + char *filter = NULL; + + sid_str = sid_string_talloc(frame, sid); + BAIL_ON_PTR_ERROR(sid_str, nt_status); + + filter = talloc_asprintf(frame, "(keywords=backLink=%s)", + sid_str); + BAIL_ON_PTR_ERROR(filter, nt_status); + + base = cell_search_base(dc->domain_cell); + BAIL_ON_PTR_ERROR(base, nt_status); + + nt_status = dc_do_search(dc, base, LDAP_SCOPE_SUBTREE, + filter, attrs, msg); + } + BAIL_ON_NTSTATUS_ERROR(nt_status); + + *cell = dc->domain_cell; + +done: + talloc_destroy(CONST_DISCARD(char*, base)); + talloc_destroy(frame); + + return nt_status; +} diff --git a/source3/winbindd/idmap_adex/gc_util.c b/source3/winbindd/idmap_adex/gc_util.c new file mode 100644 index 0000000000..6dc02336d5 --- /dev/null +++ b/source3/winbindd/idmap_adex/gc_util.c @@ -0,0 +1,848 @@ +/* + * idmap_adex: Global Catalog search interface + * + * Copyright (C) Gerald (Jerry) Carter 2007-2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "idmap_adex.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +static struct gc_info *_gc_server_list = NULL; + + +/********************************************************************** + *********************************************************************/ + +static struct gc_info *gc_list_head(void) +{ + return _gc_server_list; +} + +/********************************************************************** + Checks if either of the domains is a subdomain of the other + *********************************************************************/ + +static bool is_subdomain(const char* a, const char *b) +{ + char *s; + TALLOC_CTX *frame = talloc_stackframe(); + char *x, *y; + bool ret = false; + + /* Trivial cases */ + + if (!a && !b) + return true; + + if (!a || !b) + return false; + + /* Normalize the case */ + + x = talloc_strdup(frame, a); + y = talloc_strdup(frame, b); + if (!x || !y) { + ret = false; + goto done; + } + + strupper_m(x); + strupper_m(y); + + /* Exact match */ + + if (strcmp(x, y) == 0) { + ret = true; + goto done; + } + + /* Check for trailing substrings */ + + s = strstr_m(x, y); + if (s && (strlen(s) == strlen(y))) { + ret = true; + goto done; + } + + s = strstr_m(y, x); + if (s && (strlen(s) == strlen(x))) { + ret = true; + goto done; + } + +done: + talloc_destroy(frame); + + return ret; +} + +/********************************************************************** + *********************************************************************/ + + NTSTATUS gc_find_forest_root(struct gc_info *gc, const char *domain) +{ + ADS_STRUCT *ads = NULL; + ADS_STATUS ads_status; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct NETLOGON_SAM_LOGON_RESPONSE_EX cldap_reply; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!gc || !domain) { + return NT_STATUS_INVALID_PARAMETER; + } + + ZERO_STRUCT(cldap_reply); + + ads = ads_init(domain, NULL, NULL); + BAIL_ON_PTR_ERROR(ads, nt_status); + + ads->auth.flags = ADS_AUTH_NO_BIND; + ads_status = ads_connect(ads); + if (!ADS_ERR_OK(ads_status)) { + DEBUG(4, ("find_forest_root: ads_connect(%s) failed! (%s)\n", + domain, ads_errstr(ads_status))); + } + nt_status = ads_ntstatus(ads_status); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if (!ads_cldap_netlogon_5(frame, + ads->config.ldap_server_name, + ads->config.realm, + &cldap_reply)) + { + DEBUG(4,("find_forest_root: Failed to get a CLDAP reply from %s!\n", + ads->server.ldap_server)); + nt_status = NT_STATUS_IO_TIMEOUT; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + gc->forest_name = talloc_strdup(gc, cldap_reply.forest); + BAIL_ON_PTR_ERROR(gc->forest_name, nt_status); + +done: + if (ads) { + ads_destroy(&ads); + } + + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS gc_add_forest(const char *domain) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct gc_info *gc = NULL; + struct gc_info *find_gc = NULL; + char *dn; + ADS_STRUCT *ads = NULL; + struct likewise_cell *primary_cell = NULL; + + primary_cell = cell_list_head(); + if (!primary_cell) { + nt_status = NT_STATUS_INVALID_SERVER_STATE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Check for duplicates based on domain name first as this + requires no connection */ + + find_gc = gc_list_head(); + while (find_gc) { + if (strequal (find_gc->forest_name, domain)) + break; + find_gc = find_gc->next; + } + + if (find_gc) { + DEBUG(10,("gc_add_forest: %s already in list\n", find_gc->forest_name)); + return NT_STATUS_OK; + } + + if ((gc = TALLOC_ZERO_P(NULL, struct gc_info)) == NULL) { + nt_status = NT_STATUS_NO_MEMORY; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Query the rootDSE for the forest root naming conect first. + Check that the a GC server for the forest has not already + been added */ + + nt_status = gc_find_forest_root(gc, domain); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + find_gc = gc_list_head(); + while (find_gc) { + if (strequal (find_gc->forest_name, gc->forest_name)) + break; + find_gc = find_gc->next; + } + + if (find_gc) { + DEBUG(10,("gc_add_forest: Forest %s already in list\n", + find_gc->forest_name)); + return NT_STATUS_OK; + } + + /* Not found, so add it here. Make sure we connect to + a DC in _this_ domain and not the forest root. */ + + dn = ads_build_dn(gc->forest_name); + BAIL_ON_PTR_ERROR(dn, nt_status); + + gc->search_base = talloc_strdup(gc, dn); + SAFE_FREE(dn); + BAIL_ON_PTR_ERROR(gc->search_base, nt_status); + +#if 0 + /* Can't use cell_connect_dn() here as there is no way to + specifiy the LWCELL_FLAG_GC_CELL flag setting for cell_connect() */ + + nt_status = cell_connect_dn(&gc->forest_cell, gc->search_base); + BAIL_ON_NTSTATUS_ERROR(nt_status); +#else + + gc->forest_cell = cell_new(); + BAIL_ON_PTR_ERROR(gc->forest_cell, nt_status); + + /* Set the DNS domain, dn, etc ... and add it to the list */ + + cell_set_dns_domain(gc->forest_cell, gc->forest_name); + cell_set_dn(gc->forest_cell, gc->search_base); + cell_set_flags(gc->forest_cell, LWCELL_FLAG_GC_CELL); +#endif + + /* It is possible to belong to a non-forest cell and a + non-provisioned forest (at our domain levele). In that + case, we should just inherit the flags from our primary + cell since the GC searches will match our own schema + model. */ + + if (strequal(primary_cell->forest_name, gc->forest_name) + || is_subdomain(primary_cell->dns_domain, gc->forest_name)) + { + cell_set_flags(gc->forest_cell, cell_flags(primary_cell)); + } else { + /* outside of our domain */ + + nt_status = cell_connect(gc->forest_cell); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = cell_lookup_settings(gc->forest_cell); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* Drop the connection now that we have the settings */ + + ads = cell_connection(gc->forest_cell); + ads_destroy(&ads); + cell_set_connection(gc->forest_cell, NULL); + } + + DLIST_ADD_END(_gc_server_list, gc, struct gc_info*); + + DEBUG(10,("gc_add_forest: Added %s to Global Catalog list of servers\n", + gc->forest_name)); + + nt_status = NT_STATUS_OK; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_destroy(gc); + DEBUG(3,("LWI: Failed to add new GC connection for %s (%s)\n", + domain, nt_errstr(nt_status))); + } + + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static void gc_server_list_destroy(void) +{ + struct gc_info *gc = gc_list_head(); + + while (gc) { + struct gc_info *p = gc->next; + + cell_destroy(gc->forest_cell); + talloc_destroy(gc); + + gc = p; + } + + _gc_server_list = NULL; + + return; +} + +/********************************************************************** + Setup the initial list of forests and initial the forest cell + settings for each. FIXME!!! + *********************************************************************/ + + NTSTATUS gc_init_list(void) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct winbindd_tdc_domain *domains = NULL; + size_t num_domains = 0; + int i; + + if (_gc_server_list != NULL) { + gc_server_list_destroy(); + } + + if (!wcache_tdc_fetch_list(&domains, &num_domains)) { + nt_status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Find our forest first. Have to try all domains here starting + with our own. gc_add_forest() filters duplicates */ + + nt_status = gc_add_forest(lp_realm()); + WARN_ON_NTSTATUS_ERROR(nt_status); + + for (i=0; i<num_domains; i++) { + uint32_t flags = (NETR_TRUST_FLAG_IN_FOREST); + + /* I think we should be able to break out of loop once + we add a GC for our forest and not have to test every one. + In fact, this entire loop is probably irrelevant since + the GC location code should always find a GC given lp_realm(). + Will have to spend time testing before making the change. + --jerry */ + + if ((domains[i].trust_flags & flags) == flags) { + nt_status = gc_add_forest(domains[i].dns_name); + WARN_ON_NTSTATUS_ERROR(nt_status); + /* Don't BAIL here since not every domain may + have a GC server */ + } + } + + /* Now add trusted forests. gc_add_forest() will filter out + duplicates. Check everything with an incoming trust path + that is not in our own forest. */ + + for (i=0; i<num_domains; i++) { + uint32_t flags = domains[i].trust_flags; + uint32_t attribs = domains[i].trust_attribs; + + /* Skip non_AD domains */ + + if (strlen(domains[i].dns_name) == 0) { + continue; + } + + /* Only add a GC for a forest outside of our own. + Ignore QUARANTINED/EXTERNAL trusts */ + + if ((flags & NETR_TRUST_FLAG_INBOUND) + && !(flags & NETR_TRUST_FLAG_IN_FOREST) + && (attribs & NETR_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) + { + nt_status = gc_add_forest(domains[i].dns_name); + WARN_ON_NTSTATUS_ERROR(nt_status); + } + } + + nt_status = NT_STATUS_OK; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(2,("LWI: Failed to initialized GC list (%s)\n", + nt_errstr(nt_status))); + } + + TALLOC_FREE(domains); + + return nt_status; +} + + +/********************************************************************** + *********************************************************************/ + + struct gc_info *gc_search_start(void) +{ + NTSTATUS nt_status = NT_STATUS_OK; + struct gc_info *gc = gc_list_head(); + + if (!gc) { + nt_status = gc_init_list(); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + gc = gc_list_head(); + } + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(2,("LWI: Failed to initialize GC list (%s)\n", + nt_errstr(nt_status))); + } + + return gc; +} + +/********************************************************************** + Search Global Catalog. Always search our own forest. The flags set + controls whether or not we search cross forest. Assume that the + resulting set is always returned from one GC so that we don't have to + both combining the LDAPMessage * results + *********************************************************************/ + + NTSTATUS gc_search_forest(struct gc_info *gc, + LDAPMessage **msg, + const char *filter) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + const char *attrs[] = {"*", NULL}; + LDAPMessage *m = NULL; + + if (!gc || !msg || !filter) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* When you have multiple domain trees in a forest, the + GC will search all naming contexts when you send it + and empty ("") base search suffix. Tested against + Windows 2003. */ + + ads_status = cell_do_search(gc->forest_cell, "", + LDAP_SCOPE_SUBTREE, filter, attrs, &m); + nt_status = ads_ntstatus(ads_status); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + *msg = m; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(2,("LWI: Forest wide search %s failed (%s)\n", + filter, nt_errstr(nt_status))); + } + + return nt_status; +} + +/********************************************************************** + Search all forests via GC and return the results in an array of + ADS_STRUCT/LDAPMessage pairs. + *********************************************************************/ + + NTSTATUS gc_search_all_forests(const char *filter, + ADS_STRUCT ***ads_list, + LDAPMessage ***msg_list, + int *num_resp, uint32_t flags) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct gc_info *gc = NULL; + uint32_t test_flags = ADEX_GC_SEARCH_CHECK_UNIQUE; + + *ads_list = NULL; + *msg_list = NULL; + *num_resp = 0; + + if ((gc = gc_search_start()) == NULL) { + nt_status = NT_STATUS_INVALID_DOMAIN_STATE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + while (gc) { + LDAPMessage *m = NULL; + + nt_status = gc_search_forest(gc, &m, filter); + if (!NT_STATUS_IS_OK(nt_status)) { + gc = gc->next; + continue; + } + + nt_status = add_ads_result_to_array(cell_connection(gc->forest_cell), + m, ads_list, msg_list, + num_resp); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* If there can only be one match, then we are done */ + + if ((*num_resp > 0) && ((flags & test_flags) == test_flags)) { + break; + } + + gc = gc->next; + } + + if (*num_resp == 0) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + nt_status = NT_STATUS_OK; + +done: + return nt_status; +} + +/********************************************************************** + Search all forests via GC and return the results in an array of + ADS_STRUCT/LDAPMessage pairs. + *********************************************************************/ + + NTSTATUS gc_search_all_forests_unique(const char *filter, + ADS_STRUCT **ads, + LDAPMessage **msg) +{ + ADS_STRUCT **ads_list = NULL; + LDAPMessage **msg_list = NULL; + int num_resp; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + nt_status = gc_search_all_forests(filter, &ads_list, + &msg_list, &num_resp, + ADEX_GC_SEARCH_CHECK_UNIQUE); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = check_result_unique(ads_list[0], msg_list[0]); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + *ads = ads_list[0]; + *msg = msg_list[0]; + +done: + /* Be care that we don't free the msg result being returned */ + + if (!NT_STATUS_IS_OK(nt_status)) { + free_result_array(ads_list, msg_list, num_resp); + } else { + talloc_destroy(ads_list); + talloc_destroy(msg_list); + } + + return nt_status; +} + +/********************************************************************* + ********************************************************************/ + + NTSTATUS gc_name_to_sid(const char *domain, + const char *name, + DOM_SID *sid, + enum lsa_SidType *sid_type) +{ + TALLOC_CTX *frame = talloc_stackframe(); + char *p, *name_user; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + char *name_filter; + ADS_STRUCT *ads = NULL; + LDAPMessage *msg = NULL; + LDAPMessage *e = NULL; + char *dn = NULL; + char *dns_domain = NULL; + ADS_STRUCT **ads_list = NULL; + LDAPMessage **msg_list = NULL; + int num_resp = 0; + int i; + + /* Strip the "DOMAIN\" prefix if necessary and search for + a matching sAMAccountName in the forest */ + + if ((p = strchr_m( name, '\\' )) == NULL) + name_user = talloc_strdup( frame, name ); + else + name_user = talloc_strdup( frame, p+1 ); + BAIL_ON_PTR_ERROR(name_user, nt_status); + + name_filter = talloc_asprintf(frame, "(sAMAccountName=%s)", name_user); + BAIL_ON_PTR_ERROR(name_filter, nt_status); + + nt_status = gc_search_all_forests(name_filter, &ads_list, + &msg_list, &num_resp, 0); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* Assume failure until we know otherwise*/ + + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + + /* Match the domain name from the DN */ + + for (i=0; i<num_resp; i++) { + ads = ads_list[i]; + msg = msg_list[i]; + + e = ads_first_entry(ads, msg); + while (e) { + struct winbindd_tdc_domain *domain_rec; + + dn = ads_get_dn(ads, e); + BAIL_ON_PTR_ERROR(dn, nt_status); + + dns_domain = cell_dn_to_dns(dn); + SAFE_FREE(dn); + BAIL_ON_PTR_ERROR(dns_domain, nt_status); + + domain_rec = wcache_tdc_fetch_domain(frame, dns_domain); + SAFE_FREE(dns_domain); + + /* Ignore failures and continue the search */ + + if (!domain_rec) { + e = ads_next_entry(ads, e); + continue; + } + + /* Check for a match on the domain name */ + + if (strequal(domain, domain_rec->domain_name)) { + if (!ads_pull_sid(ads, e, "objectSid", sid)) { + nt_status = NT_STATUS_INVALID_SID; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + talloc_destroy(domain_rec); + + nt_status = get_sid_type(ads, msg, sid_type); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* We're done! */ + nt_status = NT_STATUS_OK; + break; + } + + /* once more around thew merry-go-round */ + + talloc_destroy(domain_rec); + e = ads_next_entry(ads, e); + } + } + +done: + free_result_array(ads_list, msg_list, num_resp); + talloc_destroy(frame); + + return nt_status; +} + +/******************************************************************** + Pull an attribute string value + *******************************************************************/ + +static NTSTATUS get_object_account_name(ADS_STRUCT *ads, + LDAPMessage *msg, + char **name) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + char *sam_name = NULL; + struct winbindd_tdc_domain *domain_rec = NULL; + char *dns_domain = NULL; + char *dn = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + int len; + + /* Check parameters */ + + if (!ads || !msg || !name) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* get the name and domain */ + + dn = ads_get_dn(ads, msg); + BAIL_ON_PTR_ERROR(dn, nt_status); + + DEBUG(10,("get_object_account_name: dn = \"%s\"\n", dn)); + + dns_domain = cell_dn_to_dns(dn); + SAFE_FREE(dn); + BAIL_ON_PTR_ERROR(dns_domain, nt_status); + + domain_rec = wcache_tdc_fetch_domain(frame, dns_domain); + SAFE_FREE(dns_domain); + + if (!domain_rec) { + nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + sam_name = ads_pull_string(ads, frame, msg, "sAMAccountName"); + BAIL_ON_PTR_ERROR(sam_name, nt_status); + + len = asprintf(name, "%s\\%s", domain_rec->domain_name, sam_name); + if (len == -1) { + *name = NULL; + BAIL_ON_PTR_ERROR((*name), nt_status); + } + + nt_status = NT_STATUS_OK; + +done: + talloc_destroy(frame); + + return nt_status; +} + +/********************************************************************* + ********************************************************************/ + + NTSTATUS gc_sid_to_name(const DOM_SID *sid, + char **name, + enum lsa_SidType *sid_type) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + char *filter; + ADS_STRUCT *ads = NULL; + LDAPMessage *msg = NULL; + char *sid_string; + + *name = NULL; + + sid_string = sid_binstring(sid); + BAIL_ON_PTR_ERROR(sid_string, nt_status); + + filter = talloc_asprintf(frame, "(objectSid=%s)", sid_string); + SAFE_FREE(sid_string); + BAIL_ON_PTR_ERROR(filter, nt_status); + + nt_status = gc_search_all_forests_unique(filter, &ads, &msg); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = get_object_account_name(ads, msg, name); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = get_sid_type(ads, msg, sid_type); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + ads_msgfree(ads, msg); + talloc_destroy(frame); + + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + + NTSTATUS add_ads_result_to_array(ADS_STRUCT *ads, + LDAPMessage *msg, + ADS_STRUCT ***ads_list, + LDAPMessage ***msg_list, + int *size) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + ADS_STRUCT **ads_tmp = NULL; + LDAPMessage **msg_tmp = NULL; + int count = *size; + + if (!ads || !msg) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + +#if 0 + /* Don't add a response with no entries */ + + if (ads_count_replies(ads, msg) == 0) { + return NT_STATUS_OK; + } +#endif + + if (count == 0) { + ads_tmp = TALLOC_ARRAY(NULL, ADS_STRUCT*, 1); + BAIL_ON_PTR_ERROR(ads_tmp, nt_status); + + msg_tmp = TALLOC_ARRAY(NULL, LDAPMessage*, 1); + BAIL_ON_PTR_ERROR(msg_tmp, nt_status); + } else { + ads_tmp = TALLOC_REALLOC_ARRAY(*ads_list, *ads_list, ADS_STRUCT*, + count+1); + BAIL_ON_PTR_ERROR(ads_tmp, nt_status); + + msg_tmp = TALLOC_REALLOC_ARRAY(*msg_list, *msg_list, LDAPMessage*, + count+1); + BAIL_ON_PTR_ERROR(msg_tmp, nt_status); + } + + ads_tmp[count] = ads; + msg_tmp[count] = msg; + count++; + + *ads_list = ads_tmp; + *msg_list = msg_tmp; + *size = count; + + nt_status = NT_STATUS_OK; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_destroy(ads_tmp); + talloc_destroy(msg_tmp); + } + + return nt_status; +} + +/********************************************************************** + Frees search results. Do not free the ads_list as these are + references back to the GC search structures. + *********************************************************************/ + + void free_result_array(ADS_STRUCT **ads_list, + LDAPMessage **msg_list, + int num_resp) +{ + int i; + + for (i=0; i<num_resp; i++) { + ads_msgfree(ads_list[i], msg_list[i]); + } + + talloc_destroy(ads_list); + talloc_destroy(msg_list); +} + +/********************************************************************** + Check that we have exactly one entry from the search + *********************************************************************/ + + NTSTATUS check_result_unique(ADS_STRUCT *ads, LDAPMessage *msg) +{ + NTSTATUS nt_status; + int count; + + count = ads_count_replies(ads, msg); + + if (count <= 0) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + if (count > 1) { + nt_status = NT_STATUS_DUPLICATE_NAME; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + nt_status = NT_STATUS_OK; + +done: + return nt_status; +} diff --git a/source3/winbindd/idmap_adex/idmap_adex.c b/source3/winbindd/idmap_adex/idmap_adex.c new file mode 100644 index 0000000000..7596b1cbd8 --- /dev/null +++ b/source3/winbindd/idmap_adex/idmap_adex.c @@ -0,0 +1,460 @@ +/* + * idmap_adex: Support for D Forests + * + * Copyright (C) Gerald (Jerry) Carter 2006-2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "idmap_adex.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +#define WINBIND_CCACHE_NAME "MEMORY:winbind_ccache" + +NTSTATUS init_module(void); + +/* + * IdMap backend + */ + +/******************************************************************** + Basic init function responsible for determining our current mode + (standalone or using Centeris Cells). This must return success or + it will be dropped from the idmap backend list. + *******************************************************************/ + +static NTSTATUS _idmap_adex_init(struct idmap_domain *dom, + const char *params) +{ + ADS_STRUCT *ads = NULL; + ADS_STATUS status; + static NTSTATUS init_status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + DOM_SID domain_sid; + fstring dcname; + struct sockaddr_storage ip; + struct likewise_cell *lwcell; + + if (NT_STATUS_IS_OK(init_status)) + return NT_STATUS_OK; + + /* Silently fail if we are not a member server in security = ads */ + + if ((lp_server_role() != ROLE_DOMAIN_MEMBER) || + (lp_security() != SEC_ADS)) { + init_status = NT_STATUS_INVALID_SERVER_STATE; + BAIL_ON_NTSTATUS_ERROR(init_status); + } + + /* fetch our domain SID first */ + + if (!secrets_fetch_domain_sid(lp_workgroup(), &domain_sid)) { + init_status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + BAIL_ON_NTSTATUS_ERROR(init_status); + } + + /* reuse the same ticket cache as winbindd */ + + setenv("KRB5CCNAME", WINBIND_CCACHE_NAME, 1); + + /* Establish a connection to a DC */ + + if ((ads = ads_init(lp_realm(), lp_workgroup(), NULL)) == NULL) { + init_status = NT_STATUS_NO_MEMORY; + BAIL_ON_NTSTATUS_ERROR(init_status); + } + + ads->auth.password = + secrets_fetch_machine_password(lp_workgroup(), NULL, NULL); + ads->auth.realm = SMB_STRDUP(lp_realm()); + + /* get the DC name here to setup the server affinity cache and + local krb5.conf */ + + get_dc_name(lp_workgroup(), lp_realm(), dcname, &ip); + + status = ads_connect(ads); + if (!ADS_ERR_OK(status)) { + DEBUG(0, ("_idmap_adex_init: ads_connect() failed! (%s)\n", + ads_errstr(status))); + } + init_status = ads_ntstatus(status); + BAIL_ON_NTSTATUS_ERROR(init_status); + + + /* Find out cell membership */ + + init_status = cell_locate_membership(ads); + if (!NT_STATUS_IS_OK(init_status)) { + DEBUG(0,("LWI: Fail to locate cell membership (%s).", + nt_errstr(init_status))); + goto done; + } + + /* Fill in the cell information */ + + lwcell = cell_list_head(); + + init_status = cell_lookup_settings(lwcell); + BAIL_ON_NTSTATUS_ERROR(init_status); + + /* Miscellaneous setup. E.g. set up the list of GC + servers and domain list for our forest (does not actually + connect). */ + + init_status = gc_init_list(); + BAIL_ON_NTSTATUS_ERROR(init_status); + + init_status = domain_init_list(); + BAIL_ON_NTSTATUS_ERROR(init_status); + +done: + if (!NT_STATUS_IS_OK(init_status)) { + DEBUG(1,("Likewise initialization failed (%s)\n", + nt_errstr(init_status))); + } + + /* cleanup */ + + if (!NT_STATUS_IS_OK(init_status)) { + cell_list_destroy(); + + /* init_status stores the failure reason but we need to + return success or else idmap_init() will drop us from the + backend list */ + return NT_STATUS_OK; + } + + init_status = NT_STATUS_OK; + + return init_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _idmap_adex_get_sid_from_id(struct + idmap_domain + *dom, struct + id_map + **ids) +{ + int i; + bool one_mapped = false; + bool all_mapped = true; + NTSTATUS nt_status; + struct likewise_cell *cell; + + nt_status = _idmap_adex_init(dom, NULL); + if (!NT_STATUS_IS_OK(nt_status)) + return nt_status; + + if ((cell = cell_list_head()) == NULL) { + return NT_STATUS_INVALID_SERVER_STATE; + } + + /* have to work through these one by one */ + for (i = 0; ids[i]; i++) { + NTSTATUS status; + status = cell->provider->get_sid_from_id(ids[i]->sid, + ids[i]->xid.id, + ids[i]->xid.type); + /* Fail if we cannot find any DC */ + if (NT_STATUS_EQUAL + (status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + return status; + } + + if (!NT_STATUS_IS_OK(status)) { + ids[i]->status = ID_UNMAPPED; + all_mapped = false; + continue; + } + + ids[i]->status = ID_MAPPED; + one_mapped = true; + } + + return NT_STATUS_OK; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _idmap_adex_get_id_from_sid(struct + idmap_domain + *dom, struct + id_map + **ids) +{ + int i; + bool one_mapped = false; + bool all_mapped = true; + NTSTATUS nt_status; + struct likewise_cell *cell; + + nt_status = _idmap_adex_init(dom, NULL); + if (!NT_STATUS_IS_OK(nt_status)) + return nt_status; + + if ((cell = cell_list_head()) == NULL) { + return NT_STATUS_INVALID_SERVER_STATE; + } + + /* have to work through these one by one */ + for (i = 0; ids[i]; i++) { + NTSTATUS status; + status = cell->provider->get_id_from_sid(&ids[i]->xid.id, + &ids[i]->xid. + type, ids[i]->sid); + /* Fail if we cannot find any DC */ + if (NT_STATUS_EQUAL + (status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + return status; + } + + if (!NT_STATUS_IS_OK(status)) { + ids[i]->status = ID_UNMAPPED; + all_mapped = false; + continue; + } + + ids[i]->status = ID_MAPPED; + one_mapped = true; + } + + return NT_STATUS_OK; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _idmap_adex_set_mapping(struct + idmap_domain + *dom, const struct + id_map *map) +{ + DEBUG(0, ("_idmap_adex_set_mapping: not implemented\n")); + return NT_STATUS_NOT_IMPLEMENTED; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _idmap_adex_remove_mapping(struct + idmap_domain + *dom, const + struct + id_map + *map) +{ + DEBUG(0, ("_idmap_adex_remove_mapping: not implemented\n")); + return NT_STATUS_NOT_IMPLEMENTED; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _idmap_adex_dump(struct idmap_domain + *dom, struct id_map **maps, int *num_map) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _idmap_adex_close(struct idmap_domain + *dom) +{ + /* FIXME! need to do cleanup here */ + + return NT_STATUS_OK; +} + +/* + * IdMap NSS plugin + */ + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _nss_adex_init(struct nss_domain_entry + *e) +{ + return _idmap_adex_init(NULL, NULL); +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _nss_adex_get_info(struct + nss_domain_entry *e, + const DOM_SID * sid, + TALLOC_CTX * ctx, + ADS_STRUCT * ads, + LDAPMessage * msg, + char **homedir, + char **shell, char **gecos, gid_t * p_gid) +{ + NTSTATUS nt_status; + struct likewise_cell *cell; + + nt_status = _idmap_adex_init(NULL, NULL); + if (!NT_STATUS_IS_OK(nt_status)) + return nt_status; + + if ((cell = cell_list_head()) == NULL) { + return NT_STATUS_INVALID_SERVER_STATE; + } + + return cell->provider->get_nss_info(sid, ctx, homedir, + shell, gecos, p_gid); +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _nss_adex_map_to_alias(TALLOC_CTX * mem_ctx, const char + *domain, const char + *name, char **alias) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct likewise_cell *cell = NULL; + + nt_status = _idmap_adex_init(NULL, NULL); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if ((cell = cell_list_head()) == NULL) { + nt_status = NT_STATUS_INVALID_SERVER_STATE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + nt_status = cell->provider->map_to_alias(mem_ctx, domain, + name, alias); + + /* go ahead and allow the cache mgr to mark this in + negative cache */ + + if (!NT_STATUS_IS_OK(nt_status)) + nt_status = NT_STATUS_NONE_MAPPED; + +done: + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _nss_adex_map_from_alias(TALLOC_CTX * mem_ctx, const char + *domain, const char + *alias, char **name) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct likewise_cell *cell = NULL; + + nt_status = _idmap_adex_init(NULL, NULL); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if ((cell = cell_list_head()) == NULL) { + nt_status = NT_STATUS_INVALID_SERVER_STATE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + + nt_status = cell->provider->map_from_alias(mem_ctx, domain, + alias, name); + + /* go ahead and allow the cache mgr to mark this in + negative cache */ + + if (!NT_STATUS_IS_OK(nt_status)) + nt_status = NT_STATUS_NONE_MAPPED; + +done: + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _nss_adex_close(void) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/********************************************************************** + *********************************************************************/ + +static struct idmap_methods adex_idmap_methods = { + + .init = _idmap_adex_init, + .unixids_to_sids = _idmap_adex_get_sid_from_id, + .sids_to_unixids = _idmap_adex_get_id_from_sid, + .set_mapping = _idmap_adex_set_mapping, + .remove_mapping = _idmap_adex_remove_mapping, + .dump_data = _idmap_adex_dump, + .close_fn = _idmap_adex_close +}; +static struct nss_info_methods adex_nss_methods = { + .init = _nss_adex_init, + .get_nss_info = _nss_adex_get_info, + .map_to_alias = _nss_adex_map_to_alias, + .map_from_alias = _nss_adex_map_from_alias, + .close_fn = _nss_adex_close +}; + +/********************************************************************** + Register with the idmap and idmap_nss subsystems. We have to protect + against the idmap and nss_info interfaces being in a half-registered + state. + **********************************************************************/ +NTSTATUS idmap_adex_init(void) +{ + static NTSTATUS idmap_status = NT_STATUS_UNSUCCESSFUL; + static NTSTATUS nss_status = NT_STATUS_UNSUCCESSFUL; + if (!NT_STATUS_IS_OK(idmap_status)) { + idmap_status = + smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, + "adex", &adex_idmap_methods); + if (!NT_STATUS_IS_OK(idmap_status)) { + DEBUG(0, + ("idmap_centeris_init: Failed to register the adex" + "idmap plugin.\n")); + return idmap_status; + } + } + + if (!NT_STATUS_IS_OK(nss_status)) { + nss_status = + smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION, + "adex", &adex_nss_methods); + if (!NT_STATUS_IS_OK(nss_status)) { + DEBUG(0, + ("idmap_adex_init: Failed to register the adex" + "nss plugin.\n")); + return nss_status; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS nss_info_adex_init(void) +{ + return idmap_adex_init(); +} diff --git a/source3/winbindd/idmap_adex/idmap_adex.h b/source3/winbindd/idmap_adex/idmap_adex.h new file mode 100644 index 0000000000..e068d5c340 --- /dev/null +++ b/source3/winbindd/idmap_adex/idmap_adex.h @@ -0,0 +1,257 @@ +/* + * idmap_centeris: Support for Local IDs and Centeris Cell Structure + * + * Copyright (C) Gerald (Jerry) Carter 2006-2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _IDMAP_ADEX_H +#define _IDMAP_ADEX_H + +#include "winbindd/winbindd.h" + +#define ADEX_CELL_RDN "$LikewiseIdentityCell" + +#define ADEX_OC_USER "centerisLikewiseUser" +#define ADEX_OC_GROUP "centerisLikewiseGroup" + +#define AD_USER "User" +#define AD_GROUP "Group" + +#define ADEX_OC_POSIX_USER "posixAccount" +#define ADEX_OC_POSIX_GROUP "posixGroup" + +#define ADEX_ATTR_UIDNUM "uidNumber" +#define ADEX_ATTR_GIDNUM "gidNUmber" +#define ADEX_ATTR_HOMEDIR "unixHomeDirectory" +#define ADEX_ATTR_USERPW "unixUserPassword" +#define ADEX_ATTR_GROUPALIAS "groupAlias" /* Not part of RFC2307 */ +#define ADEX_ATTR_SHELL "loginShell" +#define ADEX_ATTR_GECOS "gecos" +#define ADEX_ATTR_UID "uid" +#define ADEX_ATTR_DISPLAYNAME "displayName" + +#define MIN_ID_VALUE 100 + +#define BAIL_ON_NTSTATUS_ERROR(x) \ + do { \ + if (!NT_STATUS_IS_OK(x)) { \ + DEBUG(10,("Failed! (%s)\n", nt_errstr(x))); \ + goto done; \ + } \ + } \ + while (0); \ + +#define WARN_ON_NTSTATUS_ERROR(x) \ + do { \ + if (!NT_STATUS_IS_OK(x)) { \ + DEBUG(10,("Failure ignored! (%s)\n", nt_errstr(x))); \ + } \ + } \ + while (0); \ + +#define BAIL_ON_ADS_ERROR(x) \ + do { \ + if (!ADS_ERR_OK(x)) { \ + goto done; \ + } \ + } \ + while (0); + +#define BAIL_ON_PTR_ERROR(p, x) \ + do { \ + if ((p) == NULL ) { \ + DEBUG(10,("NULL pointer!\n")); \ + x = NT_STATUS_NO_MEMORY; \ + goto done; \ + } \ + } while (0); + +#define PRINT_NTSTATUS_ERROR(x, hdr, level) \ + do { \ + if (!NT_STATUS_IS_OK(x)) { \ + DEBUG(level,("LWI ("hdr"): %s\n", nt_errstr(x))); \ + } \ + } while(0); +/* + * Cell Provider API + */ + +struct cell_provider_api { + NTSTATUS(*get_sid_from_id) (DOM_SID * sid, + uint32_t id, enum id_type type); + NTSTATUS(*get_id_from_sid) (uint32_t * id, + enum id_type * type, const DOM_SID * sid); + NTSTATUS(*get_nss_info) (const DOM_SID * sid, + TALLOC_CTX * ctx, + char **homedir, + char **shell, char **gecos, gid_t * p_gid); + NTSTATUS(*map_to_alias) (TALLOC_CTX * mem_ctx, + const char *domain, + const char *name, char **alias); + NTSTATUS(*map_from_alias) (TALLOC_CTX * mem_ctx, + const char *domain, + const char *alias, char **name); +}; + +/* registered providers */ + +extern struct cell_provider_api ccp_unified; +extern struct cell_provider_api ccp_local; + +#define LWCELL_FLAG_USE_RFC2307_ATTRS 0x00000001 +#define LWCELL_FLAG_SEARCH_FOREST 0x00000002 +#define LWCELL_FLAG_GC_CELL 0x00000004 +#define LWCELL_FLAG_LOCAL_MODE 0x00000008 + +struct likewise_cell { + struct likewise_cell *prev, *next; + ADS_STRUCT *conn; + struct likewise_cell *gc_search_cell; + DOM_SID domain_sid; + char *dns_domain; + char *forest_name; + char *dn; + struct GUID *links; /* only held by owning cell */ + size_t num_links; + uint32_t flags; + struct cell_provider_api *provider; +}; + +/* Search flags used for Global Catalog API */ + +#define ADEX_GC_SEARCH_CHECK_UNIQUE 0x00000001 + +struct gc_info { + struct gc_info *prev, *next; + char *forest_name; + char *search_base; + struct likewise_cell *forest_cell; +}; + +/* Available functions outside of idmap_lwidentity.c */ + +/* cell_util.c */ + +char *find_attr_string(char **list, size_t num_lines, const char *substr); +bool is_object_class(char **list, size_t num_lines, const char *substr); +int min_id_value(void); +char *cell_dn_to_dns(const char *dn); +NTSTATUS get_sid_type(ADS_STRUCT *ads, + LDAPMessage *msg, + enum lsa_SidType *type); + +NTSTATUS cell_locate_membership(ADS_STRUCT * ads); +NTSTATUS cell_lookup_settings(struct likewise_cell * cell); +NTSTATUS cell_follow_links(struct likewise_cell *cell); +NTSTATUS cell_set_local_provider(void); + +/* likewise_cell.c */ + +struct likewise_cell *cell_new(void); +struct likewise_cell *cell_list_head(void); + +bool cell_list_add(struct likewise_cell *cell); +bool cell_list_remove(struct likewise_cell * cell); + +void cell_list_destroy(void); +void cell_destroy(struct likewise_cell *c); +void cell_set_forest_searches(struct likewise_cell *c, + bool search); +void cell_set_dns_domain(struct likewise_cell *c, + const char *dns_domain); +void cell_set_connection(struct likewise_cell *c, + ADS_STRUCT *ads); +void cell_set_dn(struct likewise_cell *c, + const char *dn); +void cell_set_domain_sid(struct likewise_cell *c, + DOM_SID *sid); +void cell_set_flags(struct likewise_cell *c, uint32_t flags); +void cell_clear_flags(struct likewise_cell *c, uint32_t flags); + +const char* cell_search_base(struct likewise_cell *c); +const char *cell_dns_domain(struct likewise_cell *c); +ADS_STRUCT *cell_connection(struct likewise_cell *c); +bool cell_search_forest(struct likewise_cell *c); +ADS_STATUS cell_do_search(struct likewise_cell *c, + const char *search_base, + int scope, + const char *expr, + const char **attrs, + LDAPMessage ** msg); +uint32_t cell_flags(struct likewise_cell *c); + +NTSTATUS cell_connect_dn(struct likewise_cell **c, + const char *dn); +NTSTATUS cell_connect(struct likewise_cell *c); + + +/* gc_util.c */ + +NTSTATUS gc_init_list(void); + +NTSTATUS gc_find_forest_root(struct gc_info *gc, + const char *domain); + +struct gc_info *gc_search_start(void); + +NTSTATUS gc_search_forest(struct gc_info *gc, + LDAPMessage **msg, + const char *filter); + +NTSTATUS gc_search_all_forests(const char *filter, + ADS_STRUCT ***ads_list, + LDAPMessage ***msg_list, + int *num_resp, uint32_t flags); + +NTSTATUS gc_search_all_forests_unique(const char *filter, + ADS_STRUCT **ads, + LDAPMessage **msg); + +NTSTATUS gc_name_to_sid(const char *domain, + const char *name, + DOM_SID *sid, + enum lsa_SidType *sid_type); + +NTSTATUS gc_sid_to_name(const DOM_SID *sid, + char **name, + enum lsa_SidType *sid_type); + +NTSTATUS add_ads_result_to_array(ADS_STRUCT *ads, + LDAPMessage *msg, + ADS_STRUCT ***ads_list, + LDAPMessage ***msg_list, + int *size); + +void free_result_array(ADS_STRUCT **ads_list, + LDAPMessage **msg_list, + int num_resp); + +NTSTATUS check_result_unique(ADS_STRUCT *ads, + LDAPMessage *msg); + + +/* domain_util.c */ + +NTSTATUS domain_init_list(void); + +NTSTATUS dc_search_domains(struct likewise_cell **cell, + LDAPMessage **msg, + const char *dn, + const DOM_SID *user_sid); + + +#endif /* _IDMAP_ADEX_H */ diff --git a/source3/winbindd/idmap_adex/likewise_cell.c b/source3/winbindd/idmap_adex/likewise_cell.c new file mode 100644 index 0000000000..7723b3e015 --- /dev/null +++ b/source3/winbindd/idmap_adex/likewise_cell.c @@ -0,0 +1,443 @@ +/* + * idmap_adex: Support for AD Forests + * + * Copyright (C) Gerald (Jerry) Carter 2006-2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "idmap_adex.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +static struct likewise_cell *_lw_cell_list = NULL; + +/********************************************************************** + Return the current HEAD of the list + *********************************************************************/ + + struct likewise_cell *cell_list_head(void) +{ + return _lw_cell_list; +} + + +/********************************************************************** + *********************************************************************/ + + void cell_destroy(struct likewise_cell *c) +{ + if (!c) + return; + + if (c->conn) + ads_destroy(&c->conn); + + talloc_destroy(c); +} + +/********************************************************************** + Free all cell entries and reset the list head to NULL + *********************************************************************/ + + void cell_list_destroy(void) +{ + struct likewise_cell *p = _lw_cell_list; + + while (p) { + struct likewise_cell *q = p->next; + + cell_destroy(p); + + p = q; + } + + _lw_cell_list = NULL; + + return; +} + +/********************************************************************** + Add a new cell structure to the list + *********************************************************************/ + + struct likewise_cell* cell_new(void) +{ + struct likewise_cell *c; + + /* Each cell struct is a TALLOC_CTX* */ + + c = TALLOC_ZERO_P(NULL, struct likewise_cell); + if (!c) { + DEBUG(0,("cell_new: memory allocation failure!\n")); + return NULL; + } + + return c; +} + +/********************************************************************** + Add a new cell structure to the list + *********************************************************************/ + + bool cell_list_add(struct likewise_cell * cell) +{ + if (!cell) { + return false; + } + + /* Always add to the end */ + + DLIST_ADD_END(_lw_cell_list, cell, struct likewise_cell *); + + return true; +} + +/********************************************************************** + Add a new cell structure to the list + *********************************************************************/ + + bool cell_list_remove(struct likewise_cell * cell) +{ + if (!cell) { + return false; + } + + /* Remove and drop the cell structure */ + + DLIST_REMOVE(_lw_cell_list, cell); + talloc_destroy(cell); + + return true; +} + +/********************************************************************** + Set the containing DNS domain for a cell + *********************************************************************/ + + void cell_set_dns_domain(struct likewise_cell *c, const char *dns_domain) +{ + c->dns_domain = talloc_strdup(c, dns_domain); +} + +/********************************************************************** + Set ADS connection for a cell + *********************************************************************/ + + void cell_set_connection(struct likewise_cell *c, ADS_STRUCT *ads) +{ + c->conn = ads; +} + +/********************************************************************** + *********************************************************************/ + + void cell_set_flags(struct likewise_cell *c, uint32_t flags) +{ + c->flags |= flags; +} + +/********************************************************************** + *********************************************************************/ + + void cell_clear_flags(struct likewise_cell *c, uint32_t flags) +{ + c->flags &= ~flags; +} + +/********************************************************************** + Set the Cell's DN + *********************************************************************/ + + void cell_set_dn(struct likewise_cell *c, const char *dn) +{ + if ( c->dn) { + talloc_free(c->dn); + c->dn = NULL; + } + + c->dn = talloc_strdup(c, dn); +} + +/********************************************************************** + *********************************************************************/ + + void cell_set_domain_sid(struct likewise_cell *c, DOM_SID *sid) +{ + sid_copy(&c->domain_sid, sid); +} + +/* + * Query Routines + */ + +/********************************************************************** + *********************************************************************/ + + const char* cell_search_base(struct likewise_cell *c) +{ + if (!c) + return NULL; + + return talloc_asprintf(c, "cn=%s,%s", ADEX_CELL_RDN, c->dn); +} + +/********************************************************************** + *********************************************************************/ + + bool cell_search_forest(struct likewise_cell *c) +{ + uint32_t test_flags = LWCELL_FLAG_SEARCH_FOREST; + + return ((c->flags & test_flags) == test_flags); +} + +/********************************************************************** + *********************************************************************/ + + uint32_t cell_flags(struct likewise_cell *c) +{ + if (!c) + return 0; + + return c->flags; +} + +/********************************************************************** + *********************************************************************/ + + const char *cell_dns_domain(struct likewise_cell *c) +{ + if (!c) + return NULL; + + return c->dns_domain; +} + +/********************************************************************** + *********************************************************************/ + + ADS_STRUCT *cell_connection(struct likewise_cell *c) +{ + if (!c) + return NULL; + + return c->conn; +} + +/* + * Connection functions + */ + +/******************************************************************** + *******************************************************************/ + + NTSTATUS cell_connect(struct likewise_cell *c) +{ + ADS_STRUCT *ads = NULL; + ADS_STATUS ads_status; + fstring dc_name; + struct sockaddr_storage dcip; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + /* have to at least have the AD domain name */ + + if (!c->dns_domain) { + nt_status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* clear out any old information */ + + if (c->conn) { + ads_destroy(&c->conn); + c->conn = NULL; + } + + /* now setup the new connection */ + + ads = ads_init(c->dns_domain, NULL, NULL); + BAIL_ON_PTR_ERROR(ads, nt_status); + + ads->auth.password = + secrets_fetch_machine_password(lp_workgroup(), NULL, NULL); + ads->auth.realm = SMB_STRDUP(lp_realm()); + + /* Make the connection. We should already have an initial + TGT using the machine creds */ + + if (cell_flags(c) & LWCELL_FLAG_GC_CELL) { + ads_status = ads_connect_gc(ads); + } else { + /* Set up server affinity for normal cells and the client + site name cache */ + + if (!get_dc_name("", c->dns_domain, dc_name, &dcip)) { + nt_status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + ads_status = ads_connect(ads); + } + + + c->conn = ads; + + nt_status = ads_ntstatus(ads_status); + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + ads_destroy(&ads); + c->conn = NULL; + } + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + + NTSTATUS cell_connect_dn(struct likewise_cell **c, const char *dn) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct likewise_cell *new_cell = NULL; + char *dns_domain = NULL; + + if (*c || !dn) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + if ((new_cell = cell_new()) == NULL) { + nt_status = NT_STATUS_NO_MEMORY; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Set the DNS domain, dn, etc ... and add it to the list */ + + dns_domain = cell_dn_to_dns(dn); + cell_set_dns_domain(new_cell, dns_domain); + SAFE_FREE(dns_domain); + + cell_set_dn(new_cell, dn); + + nt_status = cell_connect(new_cell); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + *c = new_cell; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1,("LWI: Failled to connect to cell \"%s\" (%s)\n", + dn ? dn : "NULL", nt_errstr(nt_status))); + talloc_destroy(new_cell); + } + + return nt_status; +} + + +/******************************************************************** + *******************************************************************/ + +#define MAX_SEARCH_COUNT 2 + + ADS_STATUS cell_do_search(struct likewise_cell *c, + const char *search_base, + int scope, + const char *expr, + const char **attrs, + LDAPMessage ** msg) +{ + int search_count = 0; + ADS_STATUS status; + NTSTATUS nt_status; + + /* check for a NULL connection */ + + if (!c->conn) { + nt_status = cell_connect(c); + if (!NT_STATUS_IS_OK(nt_status)) { + status = ADS_ERROR_NT(nt_status); + return status; + } + } + + DEBUG(10, ("cell_do_search: Base = %s, Filter = %s, Scope = %d, GC = %s\n", + search_base, expr, scope, + c->conn->server.gc ? "yes" : "no")); + + /* we try multiple times in case the ADS_STRUCT is bad + and we need to reconnect */ + + while (search_count < MAX_SEARCH_COUNT) { + *msg = NULL; + status = ads_do_search(c->conn, search_base, + scope, expr, attrs, msg); + if (ADS_ERR_OK(status)) { + if (DEBUGLEVEL >= 10) { + LDAPMessage *e = NULL; + + int n = ads_count_replies(c->conn, *msg); + + DEBUG(10,("cell_do_search: Located %d entries\n", n)); + + for (e=ads_first_entry(c->conn, *msg); + e!=NULL; + e = ads_next_entry(c->conn, e)) + { + char *dn = ads_get_dn(c->conn, e); + + DEBUGADD(10,(" dn: %s\n", dn ? dn : "<NULL>")); + SAFE_FREE(dn); + } + } + + return status; + } + + + DEBUG(5, ("cell_do_search: search[%d] failed (%s)\n", + search_count, ads_errstr(status))); + + search_count++; + + /* Houston, we have a problem */ + + if (status.error_type == ENUM_ADS_ERROR_LDAP) { + switch (status.err.rc) { + case LDAP_TIMELIMIT_EXCEEDED: + case LDAP_TIMEOUT: + case -1: /* we get this error if we cannot contact + the LDAP server */ + nt_status = cell_connect(c); + if (!NT_STATUS_IS_OK(nt_status)) { + status = ADS_ERROR_NT(nt_status); + return status; + } + break; + default: + /* we're all done here */ + return status; + } + } + } + + DEBUG(5, ("cell_do_search: exceeded maximum search count!\n")); + + return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); +} diff --git a/source3/winbindd/idmap_adex/provider_unified.c b/source3/winbindd/idmap_adex/provider_unified.c new file mode 100644 index 0000000000..f18534797e --- /dev/null +++ b/source3/winbindd/idmap_adex/provider_unified.c @@ -0,0 +1,1180 @@ +/* + * idmap_adex + * + * Provider for RFC2307 and SFU AD Forests + * + * Copyright (C) Gerald (Jerry) Carter 2006-2008 + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "includes.h" +#include "idmap_adex.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +/* Information needed by the LDAP search filters */ + +enum filterType { SidFilter, IdFilter, AliasFilter }; + +struct lwcell_filter +{ + enum filterType ftype; + bool use2307; + union { + DOM_SID sid; + struct { + uint32_t id; + enum id_type type; + } id; + fstring alias; + } filter; +}; + +/******************************************************************** + *******************************************************************/ + +static char* build_id_filter(uint32_t id, + enum id_type type, + uint32_t search_flags) +{ + char *filter = NULL; + char *oc_filter, *attr_filter; + NTSTATUS nt_status; + TALLOC_CTX *frame = talloc_stackframe(); + bool use2307 = ((search_flags & LWCELL_FLAG_USE_RFC2307_ATTRS) + == LWCELL_FLAG_USE_RFC2307_ATTRS); + bool use_gc = ((search_flags & LWCELL_FLAG_SEARCH_FOREST) + == LWCELL_FLAG_SEARCH_FOREST); + const char *oc; + + /* Construct search filter for objectclass and attributes */ + + switch (type) { + case ID_TYPE_UID: + oc = ADEX_OC_USER; + if (use2307) { + oc = ADEX_OC_POSIX_USER; + if (use_gc) { + oc = AD_USER; + } + } + oc_filter = talloc_asprintf(frame, "objectclass=%s", oc); + attr_filter = talloc_asprintf(frame, "%s=%u", + ADEX_ATTR_UIDNUM, id); + break; + + case ID_TYPE_GID: + oc = ADEX_OC_GROUP; + if (use2307) { + oc = ADEX_OC_POSIX_GROUP; + if (use_gc) { + oc = AD_GROUP; + } + } + oc_filter = talloc_asprintf(frame, "objectclass=%s", oc); + attr_filter = talloc_asprintf(frame, "%s=%u", + ADEX_ATTR_GIDNUM, id); + break; + default: + return NULL; + } + + BAIL_ON_PTR_ERROR(oc_filter, nt_status); + BAIL_ON_PTR_ERROR(attr_filter, nt_status); + + /* Use "keywords=%s" for non-schema cells */ + + if (use2307) { + filter = talloc_asprintf(frame, "(&(%s)(%s))", + oc_filter, attr_filter); + } else { + filter = talloc_asprintf(frame, "(&(keywords=%s)(keywords=%s))", + oc_filter, attr_filter); + } + + talloc_destroy(oc_filter); + talloc_destroy(attr_filter); + +done: + /* Don't destroy the stackframe CTX since we are returning + memory from it */ + + return filter; +} + +/******************************************************************** + *******************************************************************/ + +static char* build_alias_filter(const char *alias, uint32_t search_flags) +{ + char *filter = NULL; + char *user_attr_filter, *group_attr_filter; + NTSTATUS nt_status; + TALLOC_CTX *frame = talloc_stackframe(); + bool use2307 = ((search_flags & LWCELL_FLAG_USE_RFC2307_ATTRS) + == LWCELL_FLAG_USE_RFC2307_ATTRS); + bool search_forest = ((search_flags & LWCELL_FLAG_SEARCH_FOREST) + == LWCELL_FLAG_SEARCH_FOREST); + + /* Construct search filter for objectclass and attributes */ + + user_attr_filter = talloc_asprintf(frame, "%s=%s", + ADEX_ATTR_UID, alias); + group_attr_filter = talloc_asprintf(frame, "%s=%s", + ADEX_ATTR_DISPLAYNAME, alias); + BAIL_ON_PTR_ERROR(user_attr_filter, nt_status); + BAIL_ON_PTR_ERROR(group_attr_filter, nt_status); + + /* Use "keywords=%s" for non-schema cells */ + + if (use2307) { + filter = talloc_asprintf(frame, + "(|(&(%s)(objectclass=%s))(&(%s)(objectclass=%s)))", + user_attr_filter, + search_forest ? AD_USER : ADEX_OC_POSIX_USER, + group_attr_filter, + search_forest ? AD_GROUP : ADEX_OC_POSIX_GROUP); + } else { + filter = talloc_asprintf(frame, + "(|(keywords=%s)(keywords=%s))", + user_attr_filter, + group_attr_filter); + } + + talloc_destroy(user_attr_filter); + talloc_destroy(group_attr_filter); + +done: + /* Don't destroy the stackframe CTX since we are returning + memory from it */ + + return filter; +} + + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS search_cell(struct likewise_cell *c, + LDAPMessage **msg, + const struct lwcell_filter *fdata) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX* frame = talloc_stackframe(); + char *filter = NULL; + const char *base = NULL; + ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + const char *attrs[] = { "*", NULL }; + int count; + char *sid_str; + + /* get the filter and other search parameters */ + + switch (fdata->ftype) { + case SidFilter: + sid_str = sid_string_talloc(frame, &fdata->filter.sid); + BAIL_ON_PTR_ERROR(sid_str, nt_status); + + filter = talloc_asprintf(frame, "(keywords=backLink=%s)", + sid_str); + break; + case IdFilter: + filter = build_id_filter(fdata->filter.id.id, + fdata->filter.id.type, + cell_flags(c)); + break; + case AliasFilter: + filter = build_alias_filter(fdata->filter.alias, + cell_flags(c)); + break; + default: + nt_status = NT_STATUS_INVALID_PARAMETER; + break; + } + BAIL_ON_PTR_ERROR(filter, nt_status); + + base = cell_search_base(c); + BAIL_ON_PTR_ERROR(base, nt_status); + + ads_status = cell_do_search(c, base, LDAP_SCOPE_SUBTREE, + filter, attrs, msg); + + nt_status = ads_ntstatus(ads_status); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* Now check that we got only one reply */ + + count = ads_count_replies(c->conn, *msg); + if (count < 1) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + if ( count > 1) { + nt_status = NT_STATUS_DUPLICATE_NAME; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + +done: + PRINT_NTSTATUS_ERROR(nt_status, "search_cell", 4); + + talloc_destroy(CONST_DISCARD(char*, base)); + talloc_destroy(frame); + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS search_domain(struct likewise_cell **cell, + LDAPMessage **msg, + const char *dn, + const DOM_SID *sid) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX* frame = talloc_stackframe(); + int count; + + nt_status = dc_search_domains(cell, msg, dn, sid); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* Now check that we got only one reply */ + + count = ads_count_replies(cell_connection(*cell), *msg); + if (count < 1) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + if ( count > 1) { + nt_status = NT_STATUS_DUPLICATE_NAME; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + +done: + PRINT_NTSTATUS_ERROR(nt_status, "search_domain", 4); + talloc_destroy(frame); + + return nt_status; +} + + +/******************************************************************** + Check that a DN is within the forest scope. + *******************************************************************/ + +static bool check_forest_scope(const char *dn) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *frame = talloc_stackframe(); + char *p = NULL; + char *q = NULL; + char *dns_domain = NULL; + struct winbindd_tdc_domain *domain; + + /* If the DN does *not* contain "$LikewiseIdentityCell", + assume this is a schema mode forest and it is in the + forest scope by definition. */ + + if ((p = strstr_m(dn, ADEX_CELL_RDN)) == NULL) { + nt_status = NT_STATUS_OK; + goto done; + } + + /* If this is a non-schema forest, then make sure that the DN + is in the form "...,cn=$LikewiseIdentityCell,DC=..." */ + + if ((q = strchr_m(p, ',')) == NULL) { + nt_status = NT_STATUS_OBJECT_NAME_INVALID; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + q++; + if (StrnCaseCmp(q, "dc=", 3) != 0) { + nt_status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + + dns_domain = cell_dn_to_dns(q); + BAIL_ON_PTR_ERROR(dns_domain, nt_status); + + domain = wcache_tdc_fetch_domain(frame, dns_domain); + if (!domain) { + nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + nt_status = NT_STATUS_OK; + +done: + talloc_destroy(frame); + SAFE_FREE(dns_domain); + + return NT_STATUS_IS_OK(nt_status); +} + + + +/******************************************************************** + Check that only one result was returned within the forest cell + scope. + *******************************************************************/ + +static NTSTATUS check_result_unique_scoped(ADS_STRUCT **ads_list, + LDAPMessage **msg_list, + int num_resp, + char **dn, + DOM_SID *user_sid) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + int i; + ADS_STRUCT *ads = NULL; + LDAPMessage *msg = NULL; + int count = 0; + char *entry_dn = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + + if (!dn || !user_sid) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + *dn = NULL; + + if (!ads_list || !msg_list || (num_resp == 0)) { + nt_status = NT_STATUS_NO_SUCH_FILE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Loop over all msgs */ + + for (i=0; i<num_resp; i++) { + LDAPMessage *e = ads_first_entry(ads_list[i], msg_list[i]); + + while (e) { + entry_dn = ads_get_dn(ads_list[i], e); + BAIL_ON_PTR_ERROR(entry_dn, nt_status); + + if (check_forest_scope(entry_dn)) { + count++; + + /* If we've already broken the condition, no + need to continue */ + + if (count > 1) { + nt_status = NT_STATUS_DUPLICATE_NAME; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + ads = ads_list[i]; + msg = e; + *dn = SMB_STRDUP(entry_dn); + BAIL_ON_PTR_ERROR((*dn), nt_status); + } + + e = ads_next_entry(ads_list[i], e); + SAFE_FREE(entry_dn); + } + } + + if (!ads || !msg) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* If we made is through the loop, then grab the user_sid and + run home to base */ + + /* + Try and get the SID from either objectSid or keywords. + We cannot use pull_sid() here since we want to try + both methods and not only one or the other (and we + have no full likewise_cell struct. + + Fail if both are unavailable + */ + + if (!ads_pull_sid(ads, msg, "objectSid", user_sid)) { + char **keywords; + char *s; + size_t num_lines = 0; + + keywords = ads_pull_strings(ads, frame, msg, "keywords", + &num_lines); + BAIL_ON_PTR_ERROR(keywords, nt_status); + + s = find_attr_string(keywords, num_lines, "backLink"); + if (!s) { + nt_status = NT_STATUS_INTERNAL_DB_CORRUPTION; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + if (!string_to_sid(user_sid, s)) { + nt_status = NT_STATUS_INVALID_SID; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + } + + nt_status = NT_STATUS_OK; + +done: + if (!NT_STATUS_IS_OK(nt_status)) { + SAFE_FREE(*dn); + } + + talloc_destroy(frame); + SAFE_FREE(entry_dn); + + return nt_status; +} + +/******************************************************************** + Search all forests. Each forest can have it's own forest-cell + settings so we have to generate the filter for each search. + We don't use gc_search_all_forests() since we may have a different + schema model in each forest and need to construct the search + filter for each GC search. + *******************************************************************/ + +static NTSTATUS search_forest(struct likewise_cell *forest_cell, + LDAPMessage **msg, + const struct lwcell_filter *fdata) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *frame = talloc_stackframe(); + char *filter = NULL; + char *dn = NULL; + struct gc_info *gc = NULL; + ADS_STRUCT **ads_list = NULL; + LDAPMessage **msg_list = NULL; + int num_resp = 0; + LDAPMessage *m; + DOM_SID user_sid; + struct likewise_cell *domain_cell = NULL; + + if ((gc = gc_search_start()) == NULL) { + nt_status = NT_STATUS_INVALID_DOMAIN_STATE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + while (gc) { + char *sid_binstr = NULL; + uint32_t flags = LWCELL_FLAG_SEARCH_FOREST; + + m = NULL; + + flags |= cell_flags(gc->forest_cell); + + switch (fdata->ftype) { + case SidFilter: + sid_binstr = sid_binstring(&fdata->filter.sid); + BAIL_ON_PTR_ERROR(sid_binstr, nt_status); + + filter = talloc_asprintf(frame, "(objectSid=%s)", sid_binstr); + SAFE_FREE(sid_binstr); + break; + case IdFilter: + filter = build_id_filter(fdata->filter.id.id, + fdata->filter.id.type, flags); + break; + case AliasFilter: + filter = build_alias_filter(fdata->filter.alias, flags); + break; + } + + /* First find the sparse object in GC */ + nt_status = gc_search_forest(gc, &m, filter); + if (!NT_STATUS_IS_OK(nt_status)) { + gc = gc->next; + continue; + } + + nt_status = add_ads_result_to_array(cell_connection(gc->forest_cell), + m, &ads_list, &msg_list, + &num_resp); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + gc = gc->next; + } + + /* Uniqueness check across forests */ + + nt_status = check_result_unique_scoped(ads_list, msg_list, num_resp, + &dn, &user_sid); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = search_domain(&domain_cell, &m, dn, &user_sid); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* Save the connection and results in the return parameters */ + + forest_cell->gc_search_cell = domain_cell; + *msg = m; + +done: + PRINT_NTSTATUS_ERROR(nt_status, "search_forest", 4); + + SAFE_FREE(dn); + + free_result_array(ads_list, msg_list, num_resp); + talloc_destroy(frame); + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS search_cell_list(struct likewise_cell **c, + LDAPMessage **m, + const struct lwcell_filter *fdata) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct likewise_cell *cell = NULL; + LDAPMessage *msg = NULL; + struct likewise_cell *result_cell = NULL; + + if ((cell = cell_list_head()) == NULL) { + nt_status = NT_STATUS_INVALID_SERVER_STATE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + while (cell) { + /* Clear any previous GC search results */ + + cell->gc_search_cell = NULL; + + if (cell_search_forest(cell)) { + nt_status = search_forest(cell, &msg, fdata); + } else { + nt_status = search_cell(cell, &msg, fdata); + } + + /* Always point to the search result cell. + In forests this might be for another domain + which means the schema model may be different */ + + result_cell = cell->gc_search_cell ? + cell->gc_search_cell : cell; + + /* Check if we are done */ + + if (NT_STATUS_IS_OK(nt_status)) { + break; + } + + /* No luck. Free memory and hit the next cell. + Forest searches always set the gc_search_cell + so give preference to that connection if possible. */ + + ads_msgfree(cell_connection(result_cell), msg); + msg = NULL; + + cell = cell->next; + } + + /* This might be assigning NULL but that is ok as long as we + give back the proper error code */ + + *c = result_cell; + *m = msg; + +done: + PRINT_NTSTATUS_ERROR(nt_status, "search_cell_list", 3); + + return nt_status; +} + +/******************************************************************** + Pull the SID from an object which is always stored in the keywords + attribute as "backLink=S-1-5-21-..." + *******************************************************************/ + +static NTSTATUS pull_sid(struct likewise_cell *c, + LDAPMessage *msg, + DOM_SID *sid) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *frame = talloc_stackframe(); + ADS_STRUCT *ads = NULL; + + ads = cell_connection(c); + + /* + We have two ways of getting the sid: + (a) from the objectSID in case of a GC search, + (b) from backLink in the case of a cell search. + Pull the keywords attributes and grab the backLink. + */ + + if (!ads_pull_sid(ads, msg, "objectSid", sid)) { + char **keywords; + char *s; + size_t num_lines = 0; + + keywords = ads_pull_strings(ads, frame, msg, + "keywords", &num_lines); + BAIL_ON_PTR_ERROR(keywords, nt_status); + + s = find_attr_string(keywords, num_lines, "backLink"); + if (!s) { + nt_status = NT_STATUS_INTERNAL_DB_CORRUPTION; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + if (!string_to_sid(sid, s)) { + nt_status = NT_STATUS_INVALID_SID; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + } + + nt_status = NT_STATUS_OK; + +done: + talloc_destroy(frame); + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS get_object_type(struct likewise_cell *c, + LDAPMessage *msg, + enum id_type *type) +{ + TALLOC_CTX *ctx = talloc_stackframe(); + char **oc_list = NULL; + NTSTATUS nt_status = NT_STATUS_OK; + size_t list_size = 0; + char *s = NULL; + ADS_STRUCT *ads = NULL; + + ads = cell_connection(c); + + /* Deal with RFC 2307 support first */ + + if (cell_flags(c) & LWCELL_FLAG_USE_RFC2307_ATTRS) { + oc_list = ads_pull_strings(ads, ctx, msg, + "objectClass", &list_size); + if (!oc_list) { + nt_status = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + /* Check for posix classes and AD classes */ + + if (is_object_class(oc_list, list_size, ADEX_OC_POSIX_USER) + || is_object_class(oc_list, list_size, AD_USER)) { + *type = ID_TYPE_UID; + } else if (is_object_class(oc_list, list_size, ADEX_OC_POSIX_GROUP) + || is_object_class(oc_list, list_size, AD_GROUP)) { + *type = ID_TYPE_GID; + } else { + *type = ID_TYPE_NOT_SPECIFIED; + nt_status = NT_STATUS_INVALID_PARAMETER; + } + } else { + /* Default to non-schema mode */ + + oc_list = ads_pull_strings(ads, ctx, msg, + "keywords", &list_size); + if (!oc_list) { + nt_status = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + s = find_attr_string(oc_list, list_size, "objectClass"); + if (!s) { + nt_status = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + if (strequal(s, ADEX_OC_USER)) { + *type = ID_TYPE_UID; + } else if (strequal(s, ADEX_OC_GROUP)) { + *type = ID_TYPE_GID; + } else { + *type = ID_TYPE_NOT_SPECIFIED; + nt_status = NT_STATUS_INVALID_PARAMETER; + } + } + + nt_status = NT_STATUS_OK; + +done: + talloc_destroy(ctx); + + return nt_status; +} + +/******************************************************************** + Pull an attribute uint32_t value + *******************************************************************/ + +static NTSTATUS get_object_uint32(struct likewise_cell *c, + LDAPMessage *msg, + const char *attrib, + uint32_t *x) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + char **keywords = NULL; + size_t list_size = 0; + TALLOC_CTX *frame = talloc_stackframe(); + ADS_STRUCT *ads = NULL; + + ads = cell_connection(c); + + /* Deal with RFC2307 schema */ + + if (cell_flags(c) & LWCELL_FLAG_USE_RFC2307_ATTRS) { + if (!ads_pull_uint32(ads, msg, attrib, x)) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + } else { + /* Non-schema mode */ + char *s = NULL; + uint32_t num; + + keywords = ads_pull_strings(ads, frame, msg, "keywords", + &list_size); + BAIL_ON_PTR_ERROR(keywords, nt_status); + + s = find_attr_string(keywords, list_size, attrib); + if (!s) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + num = strtoll(s, NULL, 10); + if (errno == ERANGE) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + *x = num; + } + + nt_status = NT_STATUS_OK; + +done: + talloc_destroy(frame); + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS get_object_id(struct likewise_cell *c, + LDAPMessage *msg, + enum id_type type, + uint32_t *id) +{ + NTSTATUS nt_status = NT_STATUS_OK; + const char *id_attr; + + /* Figure out which attribute we need to pull */ + + switch (type) { + case ID_TYPE_UID: + id_attr = ADEX_ATTR_UIDNUM; + break; + case ID_TYPE_GID: + id_attr = ADEX_ATTR_GIDNUM; + break; + default: + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + break; + } + + nt_status = get_object_uint32(c, msg, id_attr, id); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + return nt_status; +} + +/******************************************************************** + Pull the uid/gid and type from an object. This differs depending on + the cell flags. + *******************************************************************/ + +static NTSTATUS pull_id(struct likewise_cell *c, + LDAPMessage *msg, + uint32_t *id, + enum id_type *type) +{ + NTSTATUS nt_status; + + nt_status = get_object_type(c, msg, type); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = get_object_id(c, msg, *type, id); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + return nt_status; +} + +/******************************************************************** + Pull an attribute string value + *******************************************************************/ + +static NTSTATUS get_object_string(struct likewise_cell *c, + LDAPMessage *msg, + TALLOC_CTX *ctx, + const char *attrib, + char **string) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + char **keywords = NULL; + size_t list_size = 0; + TALLOC_CTX *frame = talloc_stackframe(); + ADS_STRUCT *ads = NULL; + + *string = NULL; + + ads = cell_connection(c); + + /* Deal with RFC2307 schema */ + + if (cell_flags(c) & LWCELL_FLAG_USE_RFC2307_ATTRS) { + *string = ads_pull_string(ads, ctx, msg, attrib); + } else { + /* Non-schema mode */ + + char *s = NULL; + + keywords = ads_pull_strings(ads, frame, msg, + "keywords", &list_size); + if (!keywords) { + nt_status = NT_STATUS_NO_MEMORY; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + s = find_attr_string(keywords, list_size, attrib); + if (s) { + *string = talloc_strdup(ctx, s); + } + } + + if (!*string) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + nt_status = NT_STATUS_OK; + +done: + talloc_destroy(frame); + + return nt_status; +} + +/******************************************************************** + Pull the struct passwd fields for a user + *******************************************************************/ + +static NTSTATUS pull_nss_info(struct likewise_cell *c, + LDAPMessage *msg, + TALLOC_CTX *ctx, + char **homedir, + char **shell, + char **gecos, + gid_t *p_gid) +{ + NTSTATUS nt_status; + + nt_status = get_object_string(c, msg, ctx, ADEX_ATTR_HOMEDIR, homedir); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = get_object_string(c, msg, ctx, ADEX_ATTR_SHELL, shell); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = get_object_string(c, msg, ctx, ADEX_ATTR_GECOS, gecos); + /* Gecos is often not set so ignore failures */ + + nt_status = get_object_uint32(c, msg, ADEX_ATTR_GIDNUM, p_gid); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + return nt_status; +} + +/******************************************************************** + Pull the struct passwd fields for a user + *******************************************************************/ + +static NTSTATUS pull_alias(struct likewise_cell *c, + LDAPMessage *msg, + TALLOC_CTX *ctx, + char **alias) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + enum id_type type; + const char *attr = NULL; + + /* Figure out if this is a user or a group */ + + nt_status = get_object_type(c, msg, &type); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + switch (type) { + case ID_TYPE_UID: + attr = ADEX_ATTR_UID; + break; + case ID_TYPE_GID: + /* What is the group attr for RFC2307 Forests? */ + attr = ADEX_ATTR_DISPLAYNAME; + break; + default: + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + break; + } + + nt_status = get_object_string(c, msg, ctx, attr, alias); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS _ccp_get_sid_from_id(DOM_SID * sid, + uint32_t id, enum id_type type) +{ + struct likewise_cell *cell = NULL; + LDAPMessage *msg = NULL; + NTSTATUS nt_status; + struct lwcell_filter filter; + + filter.ftype = IdFilter; + filter.filter.id.id = id; + filter.filter.id.type = type; + + nt_status = search_cell_list(&cell, &msg, &filter); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = pull_sid(cell, msg, sid); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + ads_msgfree(cell->conn, msg); + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS _ccp_get_id_from_sid(uint32_t * id, + enum id_type *type, + const DOM_SID * sid) +{ + struct likewise_cell *cell = NULL; + LDAPMessage *msg = NULL; + NTSTATUS nt_status; + struct lwcell_filter filter; + + filter.ftype = SidFilter; + sid_copy(&filter.filter.sid, sid); + + nt_status = search_cell_list(&cell, &msg, &filter); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = pull_id(cell, msg, id, type); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if (*id < min_id_value()) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + +done: + ads_msgfree(cell->conn, msg); + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +static NTSTATUS _ccp_nss_get_info(const DOM_SID * sid, + TALLOC_CTX * ctx, + char **homedir, + char **shell, + char **gecos, gid_t * p_gid) +{ + struct likewise_cell *cell = NULL; + LDAPMessage *msg = NULL; + NTSTATUS nt_status; + struct lwcell_filter filter; + enum id_type type; + + filter.ftype = SidFilter; + sid_copy(&filter.filter.sid, sid); + + nt_status = search_cell_list(&cell, &msg, &filter); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = get_object_type(cell, msg, &type); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if (type != ID_TYPE_UID) { + nt_status = NT_STATUS_NO_SUCH_USER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + nt_status = pull_nss_info(cell, msg, ctx, homedir, shell, gecos, + (uint32_t*) p_gid); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + ads_msgfree(cell->conn, msg); + + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS _ccp_map_to_alias(TALLOC_CTX *ctx, + const char *domain, + const char *name, char **alias) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + DOM_SID sid; + struct likewise_cell *cell = NULL; + LDAPMessage *msg = NULL; + struct lwcell_filter filter; + enum lsa_SidType sid_type; + + /* Convert the name to a SID */ + + nt_status = gc_name_to_sid(domain, name, &sid, &sid_type); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* Find the user/group */ + + filter.ftype = SidFilter; + sid_copy(&filter.filter.sid, &sid); + + nt_status = search_cell_list(&cell, &msg, &filter); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* Pull the alias and return */ + + nt_status = pull_alias(cell, msg, ctx, alias); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + PRINT_NTSTATUS_ERROR(nt_status, "map_to_alias", 3); + + talloc_destroy(frame); + ads_msgfree(cell_connection(cell), msg); + + return nt_status; +} + +/********************************************************************** + Map from an alias name to the canonical, qualified name. + Ensure that the alias is only pull from the closest in which + the user or gorup is enabled in + *********************************************************************/ + +static NTSTATUS _ccp_map_from_alias(TALLOC_CTX *mem_ctx, + const char *domain, + const char *alias, char **name) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + DOM_SID sid; + struct likewise_cell *cell_alias = NULL; + LDAPMessage *msg_alias = NULL; + struct likewise_cell *cell_sid = NULL; + LDAPMessage *msg_sid = NULL; + struct lwcell_filter filter; + char *canonical_name = NULL; + enum lsa_SidType type; + + /* Find the user/group */ + + filter.ftype = AliasFilter; + fstrcpy(filter.filter.alias, alias); + + nt_status = search_cell_list(&cell_alias, &msg_alias, &filter); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + nt_status = pull_sid(cell_alias, msg_alias, &sid); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + /* Now search again for the SID according to the cell list. + Verify that the cell of both search results is the same + so that we only match an alias from the closest cell + in which a user/group has been instantied. */ + + filter.ftype = SidFilter; + sid_copy(&filter.filter.sid, &sid); + + nt_status = search_cell_list(&cell_sid, &msg_sid, &filter); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if (cell_alias != cell_sid) { + nt_status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Finally do the GC sid/name conversion */ + + nt_status = gc_sid_to_name(&sid, &canonical_name, &type); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + *name = talloc_strdup(mem_ctx, canonical_name); + BAIL_ON_PTR_ERROR((*name), nt_status); + + nt_status = NT_STATUS_OK; + +done: + PRINT_NTSTATUS_ERROR(nt_status, "map_from_alias", 3); + + ads_msgfree(cell_connection(cell_alias), msg_alias); + ads_msgfree(cell_connection(cell_sid), msg_sid); + + SAFE_FREE(canonical_name); + + talloc_destroy(frame); + + return nt_status; +} + +/******************************************************************** + *******************************************************************/ + +struct cell_provider_api ccp_unified = { + .get_sid_from_id = _ccp_get_sid_from_id, + .get_id_from_sid = _ccp_get_id_from_sid, + .get_nss_info = _ccp_nss_get_info, + .map_to_alias = _ccp_map_to_alias, + .map_from_alias = _ccp_map_from_alias +}; diff --git a/source3/winbindd/idmap_hash/idmap_hash.c b/source3/winbindd/idmap_hash/idmap_hash.c new file mode 100644 index 0000000000..a050f99bc8 --- /dev/null +++ b/source3/winbindd/idmap_hash/idmap_hash.c @@ -0,0 +1,393 @@ +/* + * idmap_hash.c + * + * Copyright (C) Gerald Carter <jerry@samba.org> 2007 - 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "includes.h" +#include "winbindd/winbindd.h" +#include "idmap_hash.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +struct sid_hash_table { + DOM_SID *sid; +}; + +struct sid_hash_table *hashed_domains = NULL; + +/********************************************************************* + Hash a domain SID (S-1-5-12-aaa-bbb-ccc) to a 12bit number + ********************************************************************/ + +static uint32_t hash_domain_sid(const DOM_SID *sid) +{ + uint32_t hash; + + if (sid->num_auths != 4) + return 0; + + /* XOR the last three subauths */ + + hash = ((sid->sub_auths[1] ^ sid->sub_auths[2]) ^ sid->sub_auths[3]); + + /* Take all 32-bits into account when generating the 12-bit + hash value */ + hash = (((hash & 0xFFF00000) >> 20) + + ((hash & 0x000FFF00) >> 8) + + (hash & 0x000000FF)) & 0x0000FFF; + + /* return a 12-bit hash value */ + + return hash; +} + +/********************************************************************* + Hash a Relative ID to a 20 bit number + ********************************************************************/ + +static uint32_t hash_rid(uint32_t rid) +{ + /* 20 bits for the rid which allows us to support + the first 100K users/groups in a domain */ + + return (rid & 0x0007FFFF); +} + +/********************************************************************* + ********************************************************************/ + +static uint32_t combine_hashes(uint32_t h_domain, + uint32_t h_rid) +{ + uint32_t return_id = 0; + + /* shift the hash_domain 19 bits to the left and OR with the + hash_rid */ + + return_id = ((h_domain<<19) | h_rid); + + return return_id; +} + +/********************************************************************* + ********************************************************************/ + +static void separate_hashes(uint32_t id, + uint32_t *h_domain, + uint32_t *h_rid) +{ + *h_rid = id & 0x0007FFFF; + *h_domain = (id & 0x7FF80000) >> 19; + + return; +} + + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS be_init(struct idmap_domain *dom, + const char *params) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct winbindd_tdc_domain *dom_list = NULL; + size_t num_domains = 0; + int i; + + /* If the domain SID hash talbe has been initialized, assume + that we completed this function previously */ + + if ( hashed_domains ) { + nt_status = NT_STATUS_OK; + goto done; + } + + if (!wcache_tdc_fetch_list(&dom_list, &num_domains)) { + nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* Create the hash table of domain SIDs */ + + hashed_domains = TALLOC_ZERO_ARRAY(NULL, struct sid_hash_table, 4096); + BAIL_ON_PTR_NT_ERROR(hashed_domains, nt_status); + + /* create the hash table of domain SIDs */ + + for (i=0; i<num_domains; i++) { + uint32_t hash; + + if (is_null_sid(&dom_list[i].sid)) + continue; + if ((hash = hash_domain_sid(&dom_list[i].sid)) == 0) + continue; + + DEBUG(5,("hash:be_init() Adding %s (%s) -> %d\n", + dom_list[i].domain_name, + sid_string_dbg(&dom_list[i].sid), + hash)); + + hashed_domains[hash].sid = talloc(hashed_domains, DOM_SID); + sid_copy(hashed_domains[hash].sid, &dom_list[i].sid); + } + +done: + return nt_status; +} + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS unixids_to_sids(struct idmap_domain *dom, + struct id_map **ids) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + int i; + + nt_status = be_init(dom, NULL); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if (!ids) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + for (i=0; ids[i]; i++) { + uint32_t h_domain, h_rid; + + ids[i]->status = ID_UNMAPPED; + + separate_hashes(ids[i]->xid.id, &h_domain, &h_rid); + + /* Make sure the caller allocated memor for us */ + + if (!ids[i]->sid) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + /* If the domain hash doesn't find a SID in the table, + skip it */ + + if (!hashed_domains[h_domain].sid) + continue; + + sid_copy(ids[i]->sid, hashed_domains[h_domain].sid); + sid_append_rid(ids[i]->sid, h_rid); + ids[i]->status = ID_MAPPED; + } + +done: + return nt_status; +} + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS sids_to_unixids(struct idmap_domain *dom, + struct id_map **ids) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + int i; + + nt_status = be_init(dom, NULL); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if (!ids) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + for (i=0; ids[i]; i++) { + DOM_SID sid; + uint32_t rid; + uint32_t h_domain, h_rid; + + ids[i]->status = ID_UNMAPPED; + + sid_copy(&sid, ids[i]->sid); + sid_split_rid(&sid, &rid); + + h_domain = hash_domain_sid(&sid); + h_rid = hash_rid(rid); + + /* Check that both hashes are non-zero*/ + + if (h_domain && h_rid) { + ids[i]->xid.id = combine_hashes(h_domain, h_rid); + ids[i]->status = ID_MAPPED; + } + } + +done: + return nt_status; +} + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS be_close(struct idmap_domain *dom) +{ + if (hashed_domains) + talloc_free(hashed_domains); + + return NT_STATUS_OK; +} + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS nss_hash_init(struct nss_domain_entry *e ) +{ + return be_init(NULL, NULL); +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_hash_get_info(struct nss_domain_entry *e, + const DOM_SID *sid, + TALLOC_CTX *ctx, + ADS_STRUCT *ads, + LDAPMessage *msg, + char **homedir, + char **shell, + char **gecos, + gid_t *p_gid ) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + nt_status = nss_hash_init(e); + BAIL_ON_NTSTATUS_ERROR(nt_status); + + if (!homedir || !shell || !gecos) { + nt_status = NT_STATUS_INVALID_PARAMETER; + BAIL_ON_NTSTATUS_ERROR(nt_status); + } + + *homedir = talloc_strdup(ctx, lp_template_homedir()); + BAIL_ON_PTR_NT_ERROR(*homedir, nt_status); + + *shell = talloc_strdup(ctx, lp_template_shell()); + BAIL_ON_PTR_NT_ERROR(*shell, nt_status); + + *gecos = NULL; + + /* Initialize the gid so that the upper layer fills + in the proper Windows primary group */ + + if (*p_gid) { + *p_gid = (gid_t)-1; + } + +done: + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_hash_map_to_alias(TALLOC_CTX *mem_ctx, + const char *domain, + const char *name, + char **alias) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + const char *value; + + value = talloc_asprintf(mem_ctx, "%s\\%s", domain, name); + BAIL_ON_PTR_NT_ERROR(value, nt_status); + + nt_status = mapfile_lookup_key(mem_ctx, value, alias); + BAIL_ON_NTSTATUS_ERROR(nt_status); + +done: + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_hash_map_from_alias(TALLOC_CTX *mem_ctx, + const char *domain, + const char *alias, + char **name) +{ + return mapfile_lookup_value(mem_ctx, alias, name); +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_hash_close(void) +{ + return NT_STATUS_OK; +} + +/********************************************************************* + Dispatch Tables for IDMap and NssInfo Methods +********************************************************************/ + +static struct idmap_methods hash_idmap_methods = { + .init = be_init, + .unixids_to_sids = unixids_to_sids, + .sids_to_unixids = sids_to_unixids, + .close_fn = be_close +}; + +static struct nss_info_methods hash_nss_methods = { + .init = nss_hash_init, + .get_nss_info = nss_hash_get_info, + .map_to_alias = nss_hash_map_to_alias, + .map_from_alias = nss_hash_map_from_alias, + .close_fn = nss_hash_close +}; + +/********************************************************************** + Register with the idmap and idmap_nss subsystems. We have to protect + against the idmap and nss_info interfaces being in a half-registered + state. + **********************************************************************/ + +NTSTATUS idmap_hash_init(void) +{ + static NTSTATUS idmap_status = NT_STATUS_UNSUCCESSFUL; + static NTSTATUS nss_status = NT_STATUS_UNSUCCESSFUL; + + if ( !NT_STATUS_IS_OK(idmap_status) ) { + idmap_status = smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, + "hash", &hash_idmap_methods); + + if ( !NT_STATUS_IS_OK(idmap_status) ) { + DEBUG(0,("Failed to register hash idmap plugin.\n")); + return idmap_status; + } + } + + if ( !NT_STATUS_IS_OK(nss_status) ) { + nss_status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION, + "hash", &hash_nss_methods); + if ( !NT_STATUS_IS_OK(nss_status) ) { + DEBUG(0,("Failed to register hash idmap nss plugin.\n")); + return nss_status; + } + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/idmap_hash/idmap_hash.h b/source3/winbindd/idmap_hash/idmap_hash.h new file mode 100644 index 0000000000..621520e950 --- /dev/null +++ b/source3/winbindd/idmap_hash/idmap_hash.h @@ -0,0 +1,60 @@ +/* + * lwopen.h + * + * Copyright (C) Gerald Carter <jerry@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef _LWOPEN_H +#define _LWOPEN_H + +#define BAIL_ON_NTSTATUS_ERROR(x) \ + do { \ + if (!NT_STATUS_IS_OK(x)) { \ + DEBUG(10,("Failed! (%s)\n", nt_errstr(x))); \ + goto done; \ + } \ + } \ + while (0); \ + +#define BAIL_ON_PTR_NT_ERROR(p, x) \ + do { \ + if ((p) == NULL ) { \ + DEBUG(10,("NULL pointer!\n")); \ + x = NT_STATUS_NO_MEMORY; \ + goto done; \ + } else { \ + x = NT_STATUS_OK; \ + } \ + } while (0); + +#define PRINT_NTSTATUS_ERROR(x, hdr, level) \ + do { \ + if (!NT_STATUS_IS_OK(x)) { \ + DEBUG(level,("Likewise Open ("hdr"): %s\n", nt_errstr(x))); \ + } \ + } while(0); + + +NTSTATUS mapfile_lookup_key(TALLOC_CTX *ctx, + const char *value, + char **key); + +NTSTATUS mapfile_lookup_value(TALLOC_CTX *ctx, + const char *key, + char **value); + +#endif /* _LWOPEN_H */ diff --git a/source3/winbindd/idmap_hash/mapfile.c b/source3/winbindd/idmap_hash/mapfile.c new file mode 100644 index 0000000000..5ab1142ffe --- /dev/null +++ b/source3/winbindd/idmap_hash/mapfile.c @@ -0,0 +1,175 @@ +/* + * mapfile.c + * + * Copyright (C) Gerald Carter <jerry@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "includes.h" +#include "winbindd/winbindd.h" +#include "idmap_hash.h" +#include <stdio.h> + +XFILE *lw_map_file = NULL; + +/********************************************************************* + ********************************************************************/ + +static bool mapfile_open(void) +{ + const char *mapfile_name = NULL; + + /* If we have an open handle, just reset it */ + + if (lw_map_file) { + return (x_tseek(lw_map_file, 0, SEEK_SET) == 0); + } + + mapfile_name = lp_parm_const_string(-1, "idmap_hash", "name_map", NULL); + if (!mapfile_name) { + return false; + } + + lw_map_file = x_fopen(mapfile_name, O_RDONLY, 0); + if (!lw_map_file) { + DEBUG(0,("can't open idmap_hash:name_map (%s). Error %s\n", + mapfile_name, strerror(errno) )); + return false; + } + + return true; +} + +/********************************************************************* + ********************************************************************/ + +static bool mapfile_read_line(fstring key, fstring value) +{ + char buffer[1024]; + char *p; + int len; + + if (!lw_map_file) + return false; + + if ((p = x_fgets(buffer, sizeof(buffer)-1, lw_map_file)) == NULL) { + return false; + } + + /* Strip newlines and carriage returns */ + + len = strlen_m(buffer) - 1; + while ((buffer[len] == '\n') || (buffer[len] == '\r')) { + buffer[len--] = '\0'; + } + + + if ((p = strchr_m(buffer, '=')) == NULL ) { + DEBUG(0,("idmap_hash: Bad line in name_map (%s)\n", buffer)); + return false; + } + + *p = '\0'; + p++; + + fstrcpy(key, buffer); + fstrcpy(value, p); + + /* Eat whitespace */ + + if (!trim_char(key, ' ', ' ')) + return false; + + if (!trim_char(value, ' ', ' ')) + return false; + + return true; +} + +/********************************************************************* + ********************************************************************/ + +static bool mapfile_close(void) +{ + int ret = 0; + if (lw_map_file) { + ret = x_fclose(lw_map_file); + lw_map_file = NULL; + } + + return (ret == 0); +} + + +/********************************************************************* + ********************************************************************/ + +NTSTATUS mapfile_lookup_key(TALLOC_CTX *ctx, const char *value, char **key) +{ + fstring r_key, r_value; + NTSTATUS ret = NT_STATUS_NOT_FOUND; + + if (!mapfile_open()) + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + + while (mapfile_read_line(r_key, r_value)) + { + if (strequal(r_value, value)) { + ret = NT_STATUS_OK; + + /* We're done once finishing this block */ + *key = talloc_strdup(ctx, r_key); + if (!*key) { + ret = NT_STATUS_NO_MEMORY; + } + break; + } + } + + mapfile_close(); + + return ret; +} + +/********************************************************************* + ********************************************************************/ + +NTSTATUS mapfile_lookup_value(TALLOC_CTX *ctx, const char *key, char **value) +{ + fstring r_key, r_value; + NTSTATUS ret = NT_STATUS_NOT_FOUND; + + if (!mapfile_open()) + return NT_STATUS_OBJECT_PATH_NOT_FOUND; + + while (mapfile_read_line(r_key, r_value)) + { + if (strequal(r_key, key)) { + ret = NT_STATUS_OK; + + /* We're done once finishing this block */ + *value = talloc_strdup(ctx, r_value); + if (!*key) { + ret = NT_STATUS_NO_MEMORY; + } + break; + } + } + + mapfile_close(); + + return ret; +} diff --git a/source3/winbindd/idmap_tdb.c b/source3/winbindd/idmap_tdb.c index 9e66eed0c8..f9d3a9fbff 100644 --- a/source3/winbindd/idmap_tdb.c +++ b/source3/winbindd/idmap_tdb.c @@ -228,7 +228,7 @@ static NTSTATUS idmap_tdb_open_db(TALLOC_CTX *memctx, TDB_CONTEXT **tdbctx) goto done; } - if (!file_exist(tdbfile, &stbuf)) { + if (!file_exist_stat(tdbfile, &stbuf)) { tdb_is_new = True; } diff --git a/source3/winbindd/idmap_tdb2.c b/source3/winbindd/idmap_tdb2.c index 3066db6f3b..8bde963c60 100644 --- a/source3/winbindd/idmap_tdb2.c +++ b/source3/winbindd/idmap_tdb2.c @@ -94,12 +94,11 @@ static NTSTATUS idmap_tdb2_open_db(void) */ static NTSTATUS idmap_tdb2_alloc_load(void) { - const char *range; uid_t low_uid = 0; uid_t high_uid = 0; gid_t low_gid = 0; gid_t high_gid = 0; - uint32 low_id, high_id; + uint32 low_id; /* see if a idmap script is configured */ idmap_tdb2_state.idmap_script = lp_parm_const_string(-1, "idmap", @@ -187,6 +186,10 @@ static NTSTATUS idmap_tdb2_allocate_id(struct unixid *xid) uint32_t high_hwm; uint32_t hwm; int res; + NTSTATUS status; + + status = idmap_tdb2_open_db(); + NT_STATUS_NOT_OK_RETURN(status); /* Get current high water mark */ switch (xid->type) { @@ -264,6 +267,10 @@ static NTSTATUS idmap_tdb2_get_hwm(struct unixid *xid) const char *hwmtype; uint32_t hwm; uint32_t high_hwm; + NTSTATUS status; + + status = idmap_tdb2_open_db(); + NT_STATUS_NOT_OK_RETURN(status); /* Get current high water mark */ switch (xid->type) { @@ -451,6 +458,10 @@ static NTSTATUS idmap_tdb2_id_to_sid(struct idmap_tdb2_context *ctx, struct id_m NTSTATUS ret; TDB_DATA data; char *keystr; + NTSTATUS status; + + status = idmap_tdb2_open_db(); + NT_STATUS_NOT_OK_RETURN(status); if (!ctx || !map) { return NT_STATUS_INVALID_PARAMETER; @@ -546,6 +557,10 @@ static NTSTATUS idmap_tdb2_sid_to_id(struct idmap_tdb2_context *ctx, struct id_m TDB_DATA data; char *keystr; unsigned long rec_id = 0; + NTSTATUS status; + + status = idmap_tdb2_open_db(); + NT_STATUS_NOT_OK_RETURN(status); if ((keystr = sid_string_talloc(ctx, map->sid)) == NULL) { DEBUG(0, ("Out of memory!\n")); diff --git a/source3/winbindd/idmap_util.c b/source3/winbindd/idmap_util.c index b10a1a4ba9..9f876618be 100644 --- a/source3/winbindd/idmap_util.c +++ b/source3/winbindd/idmap_util.c @@ -121,7 +121,7 @@ backend: return NT_STATUS_NONE_MAPPED; } - idmap_cache_set_sid2uid(sid, gid); + idmap_cache_set_sid2gid(sid, gid); return NT_STATUS_OK; } diff --git a/source3/winbindd/nss_info.c b/source3/winbindd/nss_info.c index daa3dd037d..0e8cb60257 100644 --- a/source3/winbindd/nss_info.c +++ b/source3/winbindd/nss_info.c @@ -281,6 +281,47 @@ static struct nss_domain_entry *find_nss_domain( const char *domain ) /******************************************************************** *******************************************************************/ + NTSTATUS nss_map_to_alias( TALLOC_CTX *mem_ctx, const char *domain, + const char *name, char **alias ) +{ + struct nss_domain_entry *p; + struct nss_info_methods *m; + + if ( (p = find_nss_domain( domain )) == NULL ) { + DEBUG(4,("nss_map_to_alias: Failed to find nss domain pointer for %s\n", + domain )); + return NT_STATUS_NOT_FOUND; + } + + m = p->backend->methods; + + return m->map_to_alias( mem_ctx, domain, name, alias ); +} + + +/******************************************************************** + *******************************************************************/ + + NTSTATUS nss_map_from_alias( TALLOC_CTX *mem_ctx, const char *domain, + const char *alias, char **name ) +{ + struct nss_domain_entry *p; + struct nss_info_methods *m; + + if ( (p = find_nss_domain( domain )) == NULL ) { + DEBUG(4,("nss_map_from_alias: Failed to find nss domain pointer for %s\n", + domain )); + return NT_STATUS_NOT_FOUND; + } + + m = p->backend->methods; + + return m->map_from_alias( mem_ctx, domain, alias, name ); +} + +/******************************************************************** + *******************************************************************/ + NTSTATUS nss_close( const char *parameters ) { struct nss_domain_entry *p = nss_domain_list; diff --git a/source3/winbindd/nss_info_template.c b/source3/winbindd/nss_info_template.c index aaf02e4abe..d8f903ddd0 100644 --- a/source3/winbindd/nss_info_template.c +++ b/source3/winbindd/nss_info_template.c @@ -45,6 +45,8 @@ static NTSTATUS nss_template_get_info( struct nss_domain_entry *e, if ( !homedir || !shell || !gecos ) return NT_STATUS_INVALID_PARAMETER; + /* protect against home directories using whitespace in the + username */ *homedir = talloc_strdup( ctx, lp_template_homedir() ); *shell = talloc_strdup( ctx, lp_template_shell() ); *gecos = NULL; @@ -56,6 +58,28 @@ static NTSTATUS nss_template_get_info( struct nss_domain_entry *e, return NT_STATUS_OK; } +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_template_map_to_alias( TALLOC_CTX *mem_ctx, + const char *domain, + const char *name, + char **alias ) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_template_map_from_alias( TALLOC_CTX *mem_ctx, + const char *domain, + const char *alias, + char **name ) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + /************************************************************************ ***********************************************************************/ @@ -69,9 +93,11 @@ static NTSTATUS nss_template_close( void ) ***********************************************************************/ static struct nss_info_methods nss_template_methods = { - .init = nss_template_init, - .get_nss_info = nss_template_get_info, - .close_fn = nss_template_close + .init = nss_template_init, + .get_nss_info = nss_template_get_info, + .map_to_alias = nss_template_map_to_alias, + .map_from_alias = nss_template_map_from_alias, + .close_fn = nss_template_close }; NTSTATUS nss_info_template_init( void ) diff --git a/source3/winbindd/winbindd.c b/source3/winbindd/winbindd.c index 44b5415726..5d4f21a820 100644 --- a/source3/winbindd/winbindd.c +++ b/source3/winbindd/winbindd.c @@ -66,7 +66,7 @@ static bool reload_services_file(const char *logfile) if (lp_loaded()) { const char *fname = lp_configfile(); - if (file_exist(fname,NULL) && !strcsequal(fname,get_dyn_CONFIGFILE())) { + if (file_exist(fname) && !strcsequal(fname,get_dyn_CONFIGFILE())) { set_dyn_CONFIGFILE(fname); } } @@ -1120,7 +1120,7 @@ int main(int argc, char **argv, char **envp) exit(1); } - if (!directory_exist(lp_lockdir(), NULL)) { + if (!directory_exist(lp_lockdir())) { mkdir(lp_lockdir(), 0755); } diff --git a/source3/winbindd/winbindd_ads.c b/source3/winbindd/winbindd_ads.c index 894e7866b3..1febddf110 100644 --- a/source3/winbindd/winbindd_ads.c +++ b/source3/winbindd/winbindd_ads.c @@ -1023,10 +1023,11 @@ static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, DEBUG(10,("ads: lookup_groupmem: got sid %s from " "cache\n", sid_string_dbg(&sid))); sid_copy(&(*sid_mem)[*num_names], &sid); - (*names)[*num_names] = talloc_asprintf(*names, "%s%c%s", - domain_name, - *lp_winbind_separator(), - name ); + (*names)[*num_names] = fill_domain_username_talloc( + *names, + domain_name, + name, + true); (*name_types)[*num_names] = name_type; (*num_names)++; @@ -1071,11 +1072,12 @@ static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, { sid_copy(&(*sid_mem)[*num_names], &sid_mem_nocache[i]); - (*names)[*num_names] = talloc_asprintf( *names, - "%s%c%s", - domains_nocache[i], - *lp_winbind_separator(), - names_nocache[i] ); + (*names)[*num_names] = + fill_domain_username_talloc( + *names, + domains_nocache[i], + names_nocache[i], + true); (*name_types)[*num_names] = name_types_nocache[i]; (*num_names)++; } diff --git a/source3/winbindd/winbindd_async.c b/source3/winbindd/winbindd_async.c index 1481aed8e1..7500bcbe5b 100644 --- a/source3/winbindd/winbindd_async.c +++ b/source3/winbindd/winbindd_async.c @@ -366,7 +366,7 @@ static void lookupname_recv(TALLOC_CTX *mem_ctx, bool success, /******************************************************************** The lookup name call first contacts a DC in its own domain - and fallbacks to contact a DC in the forest in our domain doesn't + and fallbacks to contact a DC if the forest in our domain doesn't know the name. ********************************************************************/ diff --git a/source3/winbindd/winbindd_cache.c b/source3/winbindd/winbindd_cache.c index 2fbb01b623..360e915bc4 100644 --- a/source3/winbindd/winbindd_cache.c +++ b/source3/winbindd/winbindd_cache.c @@ -934,6 +934,8 @@ static void wcache_save_lockout_policy(struct winbindd_domain *domain, centry_free(centry); } + + static void wcache_save_password_policy(struct winbindd_domain *domain, NTSTATUS status, struct samr_DomInfo1 *policy) @@ -957,6 +959,209 @@ static void wcache_save_password_policy(struct winbindd_domain *domain, centry_free(centry); } +/*************************************************************************** + ***************************************************************************/ + +static void wcache_save_username_alias(struct winbindd_domain *domain, + NTSTATUS status, + const char *name, const char *alias) +{ + struct cache_entry *centry; + fstring uname; + + if ( (centry = centry_start(domain, status)) == NULL ) + return; + + centry_put_string( centry, alias ); + + fstrcpy(uname, name); + strupper_m(uname); + centry_end(centry, "NSS/NA/%s", uname); + + DEBUG(10,("wcache_save_username_alias: %s -> %s\n", name, alias )); + + centry_free(centry); +} + +static void wcache_save_alias_username(struct winbindd_domain *domain, + NTSTATUS status, + const char *alias, const char *name) +{ + struct cache_entry *centry; + fstring uname; + + if ( (centry = centry_start(domain, status)) == NULL ) + return; + + centry_put_string( centry, name ); + + fstrcpy(uname, alias); + strupper_m(uname); + centry_end(centry, "NSS/AN/%s", uname); + + DEBUG(10,("wcache_save_alias_username: %s -> %s\n", alias, name )); + + centry_free(centry); +} + +/*************************************************************************** + ***************************************************************************/ + +NTSTATUS resolve_username_to_alias( TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + const char *name, char **alias ) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + char *upper_name; + + if ( domain->internal ) + return NT_STATUS_NOT_SUPPORTED; + + if (!cache->tdb) + goto do_query; + + if ( (upper_name = SMB_STRDUP(name)) == NULL ) + return NT_STATUS_NO_MEMORY; + strupper_m(upper_name); + + centry = wcache_fetch(cache, domain, "NSS/NA/%s", upper_name); + + SAFE_FREE( upper_name ); + + if (!centry) + goto do_query; + + status = centry->status; + + if (!NT_STATUS_IS_OK(status)) { + centry_free(centry); + return status; + } + + *alias = centry_string( centry, mem_ctx ); + + centry_free(centry); + + DEBUG(10,("resolve_username_to_alias: [Cached] - mapped %s to %s\n", + name, *alias ? *alias : "(none)")); + + return (*alias) ? NT_STATUS_OK : NT_STATUS_OBJECT_NAME_NOT_FOUND; + +do_query: + + /* If its not in cache and we are offline, then fail */ + + if ( get_global_winbindd_state_offline() || !domain->online ) { + DEBUG(8,("resolve_username_to_alias: rejecting query " + "in offline mode\n")); + return NT_STATUS_NOT_FOUND; + } + + status = nss_map_to_alias( mem_ctx, domain->name, name, alias ); + + if ( NT_STATUS_IS_OK( status ) ) { + wcache_save_username_alias(domain, status, name, *alias); + } + + if ( NT_STATUS_EQUAL( status, NT_STATUS_NONE_MAPPED ) ) { + wcache_save_username_alias(domain, status, name, "(NULL)"); + } + + DEBUG(5,("resolve_username_to_alias: backend query returned %s\n", + nt_errstr(status))); + + if ( NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ) { + set_domain_offline( domain ); + } + + return status; +} + +/*************************************************************************** + ***************************************************************************/ + +NTSTATUS resolve_alias_to_username( TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + const char *alias, char **name ) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + char *upper_name; + + if ( domain->internal ) + return NT_STATUS_NOT_SUPPORTED; + + if (!cache->tdb) + goto do_query; + + if ( (upper_name = SMB_STRDUP(alias)) == NULL ) + return NT_STATUS_NO_MEMORY; + strupper_m(upper_name); + + centry = wcache_fetch(cache, domain, "NSS/AN/%s", upper_name); + + SAFE_FREE( upper_name ); + + if (!centry) + goto do_query; + + status = centry->status; + + if (!NT_STATUS_IS_OK(status)) { + centry_free(centry); + return status; + } + + *name = centry_string( centry, mem_ctx ); + + centry_free(centry); + + DEBUG(10,("resolve_alias_to_username: [Cached] - mapped %s to %s\n", + alias, *name ? *name : "(none)")); + + return (*name) ? NT_STATUS_OK : NT_STATUS_OBJECT_NAME_NOT_FOUND; + +do_query: + + /* If its not in cache and we are offline, then fail */ + + if ( get_global_winbindd_state_offline() || !domain->online ) { + DEBUG(8,("resolve_alias_to_username: rejecting query " + "in offline mode\n")); + return NT_STATUS_NOT_FOUND; + } + + /* an alias cannot contain a domain prefix or '@' */ + + if (strchr(alias, '\\') || strchr(alias, '@')) { + DEBUG(10,("resolve_alias_to_username: skipping fully " + "qualified name %s\n", alias)); + return NT_STATUS_OBJECT_NAME_INVALID; + } + + status = nss_map_from_alias( mem_ctx, domain->name, alias, name ); + + if ( NT_STATUS_IS_OK( status ) ) { + wcache_save_alias_username( domain, status, alias, *name ); + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + wcache_save_alias_username(domain, status, alias, "(NULL)"); + } + + DEBUG(5,("resolve_alias_to_username: backend query returned %s\n", + nt_errstr(status))); + + if ( NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ) { + set_domain_offline( domain ); + } + + return status; +} + NTSTATUS wcache_cached_creds_exist(struct winbindd_domain *domain, const DOM_SID *sid) { struct winbind_cache *cache = get_cache(domain); @@ -3257,6 +3462,48 @@ static int validate_pwinfo(TALLOC_CTX *mem_ctx, const char *keystr, return 0; } +static int validate_nss_an(TALLOC_CTX *mem_ctx, const char *keystr, + TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); + + if (!centry) { + return 1; + } + + (void)centry_string( centry, mem_ctx ); + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DEBUG(10,("validate_pwinfo: %s ok\n", keystr)); + return 0; +} + +static int validate_nss_na(TALLOC_CTX *mem_ctx, const char *keystr, + TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + struct cache_entry *centry = create_centry_validate(keystr, dbuf, state); + + if (!centry) { + return 1; + } + + (void)centry_string( centry, mem_ctx ); + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DEBUG(10,("validate_pwinfo: %s ok\n", keystr)); + return 0; +} + static int validate_trustdoms(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status *state) { @@ -3358,6 +3605,8 @@ struct key_val_struct { {"NSS/PWINFO/", validate_pwinfo}, {"TRUSTDOMS/", validate_trustdoms}, {"TRUSTDOMCACHE/", validate_trustdomcache}, + {"NSS/NA/", validate_nss_na}, + {"NSS/AN/", validate_nss_an}, {"WINBINDD_OFFLINE", validate_offline}, {WINBINDD_CACHE_VERSION_KEYSTR, validate_cache_version}, {NULL, NULL} diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c index ce851649ba..db43101a34 100644 --- a/source3/winbindd/winbindd_cm.c +++ b/source3/winbindd/winbindd_cm.c @@ -1080,7 +1080,7 @@ static bool dcip_to_name(TALLOC_CTX *mem_ctx, fstring name ) { struct ip_service ip_list; - uint32_t nt_version = NETLOGON_VERSION_1; + uint32_t nt_version = NETLOGON_NT_VERSION_1; ip_list.ss = *pss; ip_list.port = 0; diff --git a/source3/winbindd/winbindd_dual.c b/source3/winbindd/winbindd_dual.c index 63ce0e8d7f..994c94b5be 100644 --- a/source3/winbindd/winbindd_dual.c +++ b/source3/winbindd/winbindd_dual.c @@ -120,6 +120,10 @@ void async_request(TALLOC_CTX *mem_ctx, struct winbindd_child *child, SMB_ASSERT(continuation != NULL); + DEBUG(10, ("Sending request to child pid %d (domain=%s)\n", + (int)child->pid, + (child->domain != NULL) ? child->domain->name : "''")); + state = TALLOC_P(mem_ctx, struct winbindd_async_request); if (state == NULL) { @@ -196,10 +200,12 @@ static void async_request_fail(struct winbindd_async_request *state) TALLOC_FREE(state->reply_timeout_event); - SMB_ASSERT(state->child_pid != (pid_t)0); + /* If child exists and is not already reaped, + send kill signal to child. */ - /* If not already reaped, send kill signal to child. */ - if (state->child->pid == state->child_pid) { + if ((state->child->pid != (pid_t)0) && + (state->child->pid != (pid_t)-1) && + (state->child->pid == state->child_pid)) { kill(state->child_pid, SIGTERM); /* @@ -294,13 +300,27 @@ static void schedule_async_request(struct winbindd_child *child) return; /* Busy */ } + /* + * This may be a reschedule, so we might + * have an existing timeout event pending on + * the first entry in the child->requests list + * (we only send one request at a time). + * Ensure we free it before we reschedule. + * Bug #5814, from hargagan <shargagan@novell.com>. + * JRA. + */ + + TALLOC_FREE(request->reply_timeout_event); + if ((child->pid == 0) && (!fork_domain_child(child))) { - /* Cancel all outstanding requests */ + /* fork_domain_child failed. + Cancel all outstanding requests */ while (request != NULL) { /* request might be free'd in the continuation */ struct winbindd_async_request *next = request->next; - request->continuation(request->private_data, False); + + async_request_fail(request); request = next; } return; @@ -487,6 +507,17 @@ void winbind_child_died(pid_t pid) child->event.flags = 0; child->pid = 0; + if (child->requests) { + /* + * schedule_async_request() will also + * clear this event but the call is + * idempotent so it doesn't hurt to + * cover all possible future code + * paths. JRA. + */ + TALLOC_FREE(child->requests->reply_timeout_event); + } + schedule_async_request(child); } @@ -874,7 +905,7 @@ static bool calculate_next_machine_pwd_change(const char *domain, if (time(NULL) < (pass_last_set_time + timeout)) { next_change = pass_last_set_time + timeout; DEBUG(10,("machine password still valid until: %s\n", - http_timestring(next_change))); + http_timestring(talloc_tos(), next_change))); *t = timeval_set(next_change, 0); return true; } diff --git a/source3/winbindd/winbindd_group.c b/source3/winbindd/winbindd_group.c index 4d5026d158..f2b6fbefb5 100644 --- a/source3/winbindd/winbindd_group.c +++ b/source3/winbindd/winbindd_group.c @@ -35,7 +35,11 @@ static void add_member(const char *domain, const char *user, { fstring name; - fill_domain_username(name, domain, user, True); + if (domain != NULL) { + fill_domain_username(name, domain, user, True); + } else { + fstrcpy(name, user); + } safe_strcat(name, ",", sizeof(name)-1); string_append(pp_members, name); *p_num_members += 1; @@ -136,7 +140,7 @@ static void add_expanded_sid(const DOM_SID *sid, continue; } - add_member(domain->name, names[i], pp_members, p_num_members); + add_member(NULL, names[i], pp_members, p_num_members); } done: @@ -179,12 +183,32 @@ static bool fill_passdb_alias_grmem(struct winbindd_domain *domain, /* Fill a grent structure from various other information */ -static bool fill_grent(struct winbindd_gr *gr, const char *dom_name, - const char *gr_name, gid_t unix_gid) +static bool fill_grent(TALLOC_CTX *mem_ctx, struct winbindd_gr *gr, + const char *dom_name, + char *gr_name, gid_t unix_gid) { fstring full_group_name; + char *mapped_name = NULL; + struct winbindd_domain *domain = find_domain_from_name_noinit(dom_name); + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + nt_status = normalize_name_map(mem_ctx, domain, gr_name, + &mapped_name); - fill_domain_username( full_group_name, dom_name, gr_name, True ); + /* Basic whitespace replacement */ + if (NT_STATUS_IS_OK(nt_status)) { + fill_domain_username(full_group_name, dom_name, + mapped_name, true); + } + /* Mapped to an aliase */ + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) { + fstrcpy(full_group_name, mapped_name); + } + /* no change */ + else { + fill_domain_username( full_group_name, dom_name, + gr_name, True ); + } gr->gr_gid = unix_gid; @@ -280,7 +304,10 @@ static bool fill_grent_mem_domusers( TALLOC_CTX *mem_ctx, char *domainname = NULL; char *username = NULL; fstring name; + char *mapped_name = NULL; enum lsa_SidType type; + struct winbindd_domain *target_domain = NULL; + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; DEBUG(10,("fill_grent_mem_domain_users: " "sid %s in 'Domain Users' in domain %s\n", @@ -300,7 +327,24 @@ static bool fill_grent_mem_domusers( TALLOC_CTX *mem_ctx, nt_errstr(status))); return False; } - fill_domain_username(name, domain->name, username, True); + + target_domain = find_domain_from_name_noinit(domainname); + name_map_status = normalize_name_map(mem_ctx, target_domain, + username, &mapped_name); + + /* Basic whitespace replacement */ + if (NT_STATUS_IS_OK(name_map_status)) { + fill_domain_username(name, domainname, mapped_name, true); + } + /* Mapped to an alias */ + else if (NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) { + fstrcpy(name, mapped_name); + } + /* no mapping done...use original name */ + else { + fill_domain_username(name, domainname, username, true); + } + len = strlen(name); buf_len = len + 1; if (!(buf = (char *)SMB_MALLOC(buf_len))) { @@ -552,6 +596,7 @@ static bool fill_grent_mem(struct winbindd_domain *domain, uint32 n_members = 0; char **members = NULL; NTSTATUS nt_status; + int j; nt_status = expand_groups( mem_ctx, domain, glist, n_glist, @@ -562,13 +607,45 @@ static bool fill_grent_mem(struct winbindd_domain *domain, goto done; } - /* Add new group members to list */ + /* Add new group members to list. Pass through the + alias mapping function */ - nt_status = add_names_to_list( mem_ctx, &names, &num_names, - members, n_members ); - if ( !NT_STATUS_IS_OK(nt_status) ) { - result = False; - goto done; + for (j=0; j<n_members; j++) { + fstring name_domain, name_acct; + fstring qualified_name; + char *mapped_name = NULL; + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; + struct winbindd_domain *target_domain = NULL; + + if (parse_domain_user(members[j], name_domain, name_acct)) { + target_domain = find_domain_from_name_noinit(name_domain); + /* NOW WHAT ? */ + } + if (!target_domain) { + target_domain = domain; + } + + name_map_status = normalize_name_map(members, target_domain, + name_acct, &mapped_name); + + /* Basic whitespace replacement */ + if (NT_STATUS_IS_OK(name_map_status)) { + fill_domain_username(qualified_name, name_domain, + mapped_name, true); + mapped_name = qualified_name; + } + /* no mapping at all */ + else if (!NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) { + mapped_name = members[j]; + } + + nt_status = add_names_to_list( mem_ctx, &names, + &num_names, + &mapped_name, 1); + if ( !NT_STATUS_IS_OK(nt_status) ) { + result = False; + goto done; + } } TALLOC_FREE( members ); @@ -679,6 +756,7 @@ void winbindd_getgrnam(struct winbindd_cli_state *state) struct winbindd_domain *domain; fstring name_domain, name_group; char *tmp; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; /* Ensure null termination */ state->request.data.groupname[sizeof(state->request.data.groupname)-1]='\0'; @@ -686,11 +764,20 @@ void winbindd_getgrnam(struct winbindd_cli_state *state) DEBUG(3, ("[%5lu]: getgrnam %s\n", (unsigned long)state->pid, state->request.data.groupname)); - /* Parse domain and groupname */ + nt_status = normalize_name_unmap(state->mem_ctx, + state->request.data.groupname, + &tmp); + /* If we didn't map anything in the above call, just reset the + tmp pointer to the original string */ + if (!NT_STATUS_IS_OK(nt_status) && + !NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) + { + tmp = state->request.data.groupname; + } - memset(name_group, 0, sizeof(fstring)); + /* Parse domain and groupname */ - tmp = state->request.data.groupname; + memset(name_group, 0, sizeof(name_group)); name_domain[0] = '\0'; name_group[0] = '\0'; @@ -723,7 +810,7 @@ void winbindd_getgrnam(struct winbindd_cli_state *state) /* Get rid and name type from name */ - ws_name_replace( name_group, WB_REPLACE_CHAR ); + fstrcpy( name_group, tmp ); winbindd_lookupname_async( state->mem_ctx, domain->name, name_group, getgrnam_recv, WINBINDD_GETGRNAM, state ); @@ -771,7 +858,8 @@ static void getgrsid_sid2gid_recv(void *private_data, bool success, gid_t gid) return; } - if (!fill_grent(&s->state->response.data.gr, dom_name, group_name, gid) || + if (!fill_grent(s->state->mem_ctx, &s->state->response.data.gr, + dom_name, group_name, gid) || !fill_grent_mem(domain, s->state, &s->group_sid, s->group_type, &num_gr_mem, &gr_mem, &gr_mem_len)) { @@ -796,6 +884,9 @@ static void getgrsid_lookupsid_recv( void *private_data, bool success, enum lsa_SidType name_type ) { struct getgrsid_state *s = (struct getgrsid_state *)private_data; + char *mapped_name = NULL; + fstring raw_name; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; if (!success) { DEBUG(5,("getgrsid_lookupsid_recv: lookupsid failed!\n")); @@ -814,15 +905,39 @@ static void getgrsid_lookupsid_recv( void *private_data, bool success, dom_name, name, name_type)); request_error(s->state); return; -} + } - if ( (s->group_name = talloc_asprintf( s->state->mem_ctx, - "%s%c%s", - dom_name, - *lp_winbind_separator(), - name)) == NULL ) -{ - DEBUG(1, ("getgrsid_lookupsid_recv: talloc_asprintf() Failed!\n")); + /* normalize the name and ensure that we have the DOM\name + coming out of here */ + + fstrcpy(raw_name, name); + + nt_status = normalize_name_unmap(s->state->mem_ctx, raw_name, + &mapped_name); + + /* basiuc whitespace reversal */ + if (NT_STATUS_IS_OK(nt_status)) { + s->group_name = talloc_asprintf(s->state->mem_ctx, + "%s%c%s", + dom_name, + *lp_winbind_separator(), + mapped_name); + } + /* mapped from alias */ + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) { + s->group_name = mapped_name; + } + /* no mapping at all. use original string */ + else { + s->group_name = talloc_asprintf(s->state->mem_ctx, + "%s%c%s", + dom_name, + *lp_winbind_separator(), + raw_name); + } + + if (s->group_name == NULL) { + DEBUG(1, ("getgrsid_lookupsid_recv: group_name is NULL!\n")); request_error(s->state); return; } @@ -831,10 +946,10 @@ static void getgrsid_lookupsid_recv( void *private_data, bool success, winbindd_sid2gid_async(s->state->mem_ctx, &s->group_sid, getgrsid_sid2gid_recv, s); - } +} static void winbindd_getgrsid( struct winbindd_cli_state *state, const DOM_SID group_sid ) - { +{ struct getgrsid_state *s; if ( (s = TALLOC_ZERO_P(state->mem_ctx, struct getgrsid_state)) == NULL ) { @@ -1261,7 +1376,7 @@ void winbindd_getgrent(struct winbindd_cli_state *state) fill_domain_username(domain_group_name, ent->domain_name, name_list[ent->sam_entry_index].acct_name, True); - result = fill_grent(&group_list[group_list_ndx], + result = fill_grent(state->mem_ctx, &group_list[group_list_ndx], ent->domain_name, name_list[ent->sam_entry_index].acct_name, group_gid); @@ -1413,6 +1528,8 @@ static void getgroups_sid2gid_recv(void *private_data, bool success, gid_t gid); void winbindd_getgroups(struct winbindd_cli_state *state) { struct getgroups_state *s; + char *real_name = NULL; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; /* Ensure null termination */ state->request.data.username @@ -1432,13 +1549,22 @@ void winbindd_getgroups(struct winbindd_cli_state *state) s->state = state; - ws_name_return( state->request.data.username, WB_REPLACE_CHAR ); + nt_status = normalize_name_unmap(state->mem_ctx, + state->request.data.username, + &real_name); + + /* Reset the real_name pointer if we didn't do anything + productive in the above call */ + if (!NT_STATUS_IS_OK(nt_status) && + !NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) + { + real_name = state->request.data.username; + } - if (!parse_domain_user_talloc(state->mem_ctx, - state->request.data.username, + if (!parse_domain_user_talloc(state->mem_ctx, real_name, &s->domname, &s->username)) { DEBUG(5, ("Could not parse domain user: %s\n", - state->request.data.username)); + real_name)); /* error out if we do not have nested group support */ diff --git a/source3/winbindd/winbindd_locator.c b/source3/winbindd/winbindd_locator.c index b2a8bd7e30..b60d235f70 100644 --- a/source3/winbindd/winbindd_locator.c +++ b/source3/winbindd/winbindd_locator.c @@ -45,11 +45,15 @@ struct winbindd_child *locator_child(void) void winbindd_dsgetdcname(struct winbindd_cli_state *state) { - state->request.domain_name - [sizeof(state->request.domain_name)-1] = '\0'; + state->request.data.dsgetdcname.domain_name + [sizeof(state->request.data.dsgetdcname.domain_name)-1] = '\0'; + state->request.data.dsgetdcname.site_name + [sizeof(state->request.data.dsgetdcname.site_name)-1] = '\0'; + state->request.data.dsgetdcname.domain_guid + [sizeof(state->request.data.dsgetdcname.domain_guid)-1] = '\0'; DEBUG(3, ("[%5lu]: dsgetdcname for %s\n", (unsigned long)state->pid, - state->request.domain_name)); + state->request.data.dsgetdcname.domain_name)); sendto_child(state, locator_child()); } @@ -94,44 +98,59 @@ static uint32_t get_dsgetdc_flags(uint32_t wbc_flags) return ds_flags; } - static enum winbindd_result dual_dsgetdcname(struct winbindd_domain *domain, struct winbindd_cli_state *state) { NTSTATUS result; struct netr_DsRGetDCNameInfo *info = NULL; - const char *dc = NULL; uint32_t ds_flags = 0; + struct GUID guid, *guid_ptr = NULL; + const char *guid_str = NULL; - state->request.domain_name - [sizeof(state->request.domain_name)-1] = '\0'; + state->request.data.dsgetdcname.domain_name + [sizeof(state->request.data.dsgetdcname.domain_name)-1] = '\0'; + state->request.data.dsgetdcname.site_name + [sizeof(state->request.data.dsgetdcname.site_name)-1] = '\0'; + state->request.data.dsgetdcname.domain_guid + [sizeof(state->request.data.dsgetdcname.domain_guid)-1] = '\0'; DEBUG(3, ("[%5lu]: dsgetdcname for %s\n", (unsigned long)state->pid, - state->request.domain_name)); + state->request.data.dsgetdcname.domain_name)); ds_flags = get_dsgetdc_flags(state->request.flags); - result = dsgetdcname(state->mem_ctx, winbind_messaging_context(), - state->request.domain_name, - NULL, NULL, ds_flags, &info); - - if (!NT_STATUS_IS_OK(result)) { - return WINBINDD_ERROR; + result = GUID_from_string(state->request.data.dsgetdcname.domain_guid, + &guid); + if (NT_STATUS_IS_OK(result) && !GUID_all_zero(&guid)) { + guid_ptr = &guid; } - if (info->dc_address) { - dc = strip_hostname(info->dc_address); - } + result = dsgetdcname(state->mem_ctx, + winbind_messaging_context(), + state->request.data.dsgetdcname.domain_name, + guid_ptr, + state->request.data.dsgetdcname.site_name, + ds_flags, + &info); - if ((!dc || !is_ipaddress_v4(dc)) && info->dc_unc) { - dc = strip_hostname(info->dc_unc); + if (!NT_STATUS_IS_OK(result)) { + return WINBINDD_ERROR; } - if (!dc || !*dc) { + guid_str = GUID_string(state->mem_ctx, &info->domain_guid); + if (!guid_str) { return WINBINDD_ERROR; } - fstrcpy(state->response.data.dc_name, dc); + fstrcpy(state->response.data.dsgetdcname.dc_unc, info->dc_unc); + fstrcpy(state->response.data.dsgetdcname.dc_address, info->dc_address); + state->response.data.dsgetdcname.dc_address_type = info->dc_address_type; + fstrcpy(state->response.data.dsgetdcname.domain_guid, guid_str); + fstrcpy(state->response.data.dsgetdcname.domain_name, info->domain_name); + fstrcpy(state->response.data.dsgetdcname.forest_name, info->forest_name); + state->response.data.dsgetdcname.dc_flags = info->dc_flags; + fstrcpy(state->response.data.dsgetdcname.dc_site_name, info->dc_site_name); + fstrcpy(state->response.data.dsgetdcname.client_site_name, info->client_site_name); return WINBINDD_OK; } diff --git a/source3/winbindd/winbindd_pam.c b/source3/winbindd/winbindd_pam.c index d4a2e3ed79..9ff3899661 100644 --- a/source3/winbindd/winbindd_pam.c +++ b/source3/winbindd/winbindd_pam.c @@ -127,7 +127,7 @@ static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx, DATA_BLOB blob; enum ndr_err_code ndr_err; - ndr_err = ndr_push_struct_blob(&blob, mem_ctx, info3, + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, NULL, info3, (ndr_push_flags_fn_t)ndr_push_netr_SamInfo3); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { DEBUG(0,("append_info3_as_ndr: failed to append\n")); @@ -811,7 +811,9 @@ void winbindd_pam_auth(struct winbindd_cli_state *state) { struct winbindd_domain *domain; fstring name_domain, name_user; + char *mapped_user = NULL; NTSTATUS result; + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; /* Ensure null termination */ state->request.data.auth.user @@ -831,10 +833,20 @@ void winbindd_pam_auth(struct winbindd_cli_state *state) /* Parse domain and username */ - ws_name_return( state->request.data.auth.user, WB_REPLACE_CHAR ); + name_map_status = normalize_name_unmap(state->mem_ctx, + state->request.data.auth.user, + &mapped_user); - if (!canonicalize_username(state->request.data.auth.user, - name_domain, name_user)) { + /* If the name normalization didnt' actually do anything, + just use the original name */ + + if (!NT_STATUS_IS_OK(name_map_status) && + !NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) + { + mapped_user = state->request.data.auth.user; + } + + if (!canonicalize_username(mapped_user, name_domain, name_user)) { result = NT_STATUS_NO_SUCH_USER; goto done; } @@ -1447,7 +1459,10 @@ enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain, NTSTATUS result = NT_STATUS_LOGON_FAILURE; NTSTATUS krb5_result = NT_STATUS_OK; fstring name_domain, name_user; + char *mapped_user; + fstring domain_user; struct netr_SamInfo3 *info3 = NULL; + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; /* Ensure null termination */ state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0'; @@ -1465,9 +1480,26 @@ enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain, /* Parse domain and username */ - ws_name_return( state->request.data.auth.user, WB_REPLACE_CHAR ); + name_map_status = normalize_name_unmap(state->mem_ctx, + state->request.data.auth.user, + &mapped_user); - parse_domain_user(state->request.data.auth.user, name_domain, name_user); + /* If the name normalization didnt' actually do anything, + just use the original name */ + + if (!NT_STATUS_IS_OK(name_map_status) && + !NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) + { + mapped_user = state->request.data.auth.user; + } + + parse_domain_user(mapped_user, name_domain, name_user); + + if ( mapped_user != state->request.data.auth.user ) { + fstr_sprintf( domain_user, "%s\\%s", name_domain, name_user ); + safe_strcpy( state->request.data.auth.user, domain_user, + sizeof(state->request.data.auth.user)-1 ); + } if (domain->online == false) { result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; @@ -1970,14 +2002,30 @@ done: void winbindd_pam_chauthtok(struct winbindd_cli_state *state) { fstring domain, user; + char *mapped_user; struct winbindd_domain *contact_domain; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; DEBUG(3, ("[%5lu]: pam chauthtok %s\n", (unsigned long)state->pid, state->request.data.chauthtok.user)); /* Setup crap */ - ws_name_return( state->request.data.auth.user, WB_REPLACE_CHAR ); + nt_status = normalize_name_unmap(state->mem_ctx, + state->request.data.chauthtok.user, + &mapped_user); + + /* Update the chauthtok name if we did any mapping */ + + if (NT_STATUS_IS_OK(nt_status) || + NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) + { + fstrcpy(state->request.data.chauthtok.user, mapped_user); + } + + /* Must pass in state->...chauthtok.user because + canonicalize_username() assumes an fstring(). Since + we have already copied it (if necessary), this is ok. */ if (!canonicalize_username(state->request.data.chauthtok.user, domain, user)) { set_auth_errors(&state->response, NT_STATUS_NO_SUCH_USER); diff --git a/source3/winbindd/winbindd_proto.h b/source3/winbindd/winbindd_proto.h index e0fc073a0a..95ccf30cfe 100644 --- a/source3/winbindd/winbindd_proto.h +++ b/source3/winbindd/winbindd_proto.h @@ -48,8 +48,6 @@ int count_all_current_connections(void); bool claim_connection(connection_struct *conn, const char *name, uint32 msg_flags); bool register_message_flags(bool doreg, uint32 msg_flags); -bool store_pipe_opendb( smb_np_struct *p ); -bool delete_pipe_opendb( smb_np_struct *p ); /* The following definitions come from winbindd/winbindd.c */ @@ -569,6 +567,10 @@ bool parse_domain_user_talloc(TALLOC_CTX *mem_ctx, const char *domuser, void parse_add_domuser(void *buf, char *domuser, int *len); bool canonicalize_username(fstring username_inout, fstring domain, fstring user); void fill_domain_username(fstring name, const char *domain, const char *user, bool can_assume); +char *fill_domain_username_talloc(TALLOC_CTX *ctx, + const char *domain, + const char *user, + bool can_assume); const char *get_winbind_pipe_dir(void) ; char *get_winbind_priv_pipe_dir(void) ; int open_winbindd_socket(void); @@ -583,8 +585,22 @@ NTSTATUS lookup_usergroups_cached(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, const DOM_SID *user_sid, uint32 *p_num_groups, DOM_SID **user_sids); -void ws_name_replace( char *name, char replace ); -void ws_name_return( char *name, char replace ); + +NTSTATUS normalize_name_map(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + char *name, + char **normalized); +NTSTATUS normalize_name_unmap(TALLOC_CTX *mem_ctx, + char *name, + char **normalized); + +NTSTATUS resolve_username_to_alias(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + const char *name, char **alias); +NTSTATUS resolve_alias_to_username(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + const char *alias, char **name); + bool winbindd_can_contact_domain(struct winbindd_domain *domain); bool winbindd_internal_child(struct winbindd_child *child); void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain); diff --git a/source3/winbindd/winbindd_rpc.c b/source3/winbindd/winbindd_rpc.c index bb79d7ec12..d966e50159 100644 --- a/source3/winbindd/winbindd_rpc.c +++ b/source3/winbindd/winbindd_rpc.c @@ -279,6 +279,8 @@ NTSTATUS msrpc_name_to_sid(struct winbindd_domain *domain, char *full_name = NULL; struct rpc_pipe_client *cli; POLICY_HND lsa_policy; + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; + char *mapped_name = NULL; if (name == NULL || *name=='\0') { full_name = talloc_asprintf(mem_ctx, "%s", domain_name); @@ -294,9 +296,19 @@ NTSTATUS msrpc_name_to_sid(struct winbindd_domain *domain, DEBUG(3,("rpc: name_to_sid name=%s\n", full_name)); - ws_name_return( full_name, WB_REPLACE_CHAR ); + name_map_status = normalize_name_unmap(mem_ctx, full_name, + &mapped_name); - DEBUG(3,("name_to_sid [rpc] %s for domain %s\n", full_name?full_name:"", domain_name )); + /* Reset the full_name pointer if we mapped anytthing */ + + if (NT_STATUS_IS_OK(name_map_status) || + NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) + { + full_name = mapped_name; + } + + DEBUG(3,("name_to_sid [rpc] %s for domain %s\n", + full_name?full_name:"", domain_name )); result = cm_connect_lsa(domain, mem_ctx, &cli, &lsa_policy); if (!NT_STATUS_IS_OK(result)) @@ -332,6 +344,8 @@ NTSTATUS msrpc_sid_to_name(struct winbindd_domain *domain, NTSTATUS result; struct rpc_pipe_client *cli; POLICY_HND lsa_policy; + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; + char *mapped_name = NULL; DEBUG(3,("sid_to_name [rpc] %s for domain %s\n", sid_string_dbg(sid), domain->name )); @@ -356,9 +370,17 @@ NTSTATUS msrpc_sid_to_name(struct winbindd_domain *domain, *domain_name = domains[0]; *name = names[0]; - ws_name_replace( *name, WB_REPLACE_CHAR ); - DEBUG(5,("Mapped sid to [%s]\\[%s]\n", domains[0], *name)); + + name_map_status = normalize_name_map(mem_ctx, domain, *name, + &mapped_name); + if (NT_STATUS_IS_OK(name_map_status) || + NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) + { + *name = mapped_name; + DEBUG(5,("returning mapped name -- %s\n", *name)); + } + return NT_STATUS_OK; } @@ -411,8 +433,20 @@ NTSTATUS msrpc_rids_to_names(struct winbindd_domain *domain, ret_names = *names; for (i=0; i<num_rids; i++) { + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; + char *mapped_name = NULL; + if ((*types)[i] != SID_NAME_UNKNOWN) { - ws_name_replace( ret_names[i], WB_REPLACE_CHAR ); + name_map_status = normalize_name_map(mem_ctx, + domain, + ret_names[i], + &mapped_name); + if (NT_STATUS_IS_OK(name_map_status) || + NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED)) + { + ret_names[i] = mapped_name; + } + *domain_name = domains[i]; } } @@ -820,7 +854,10 @@ static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, } for (r=0; r<tmp_names.count; r++) { - (*names)[i+r] = CONST_DISCARD(char *, tmp_names.names[r].string); + (*names)[i+r] = fill_domain_username_talloc(mem_ctx, + domain->name, + tmp_names.names[r].string, + true); (*name_types)[i+r] = tmp_types.ids[r]; } @@ -981,7 +1018,7 @@ static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq) &info); if (NT_STATUS_IS_OK(result)) { - *seq = info->info2.sequence_num; + *seq = info->general.sequence_num; got_seq_num = True; } diff --git a/source3/winbindd/winbindd_user.c b/source3/winbindd/winbindd_user.c index 3b6dfdda1c..e5d0a22a73 100644 --- a/source3/winbindd/winbindd_user.c +++ b/source3/winbindd/winbindd_user.c @@ -67,12 +67,15 @@ static bool fillup_pw_field(const char *lp_template, } /* Fill a pwent structure with information we have obtained */ -static bool winbindd_fill_pwent(char *dom_name, char *user_name, +static bool winbindd_fill_pwent(TALLOC_CTX *ctx, char *dom_name, char *user_name, DOM_SID *user_sid, DOM_SID *group_sid, char *full_name, char *homedir, char *shell, struct winbindd_pw *pw) { fstring output_username; + char *mapped_name = NULL; + struct winbindd_domain *domain = NULL; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; if (!pw || !dom_name || !user_name) return False; @@ -99,7 +102,28 @@ static bool winbindd_fill_pwent(char *dom_name, char *user_name, /* Username */ - fill_domain_username(output_username, dom_name, user_name, True); + domain = find_domain_from_name_noinit(dom_name); + if (domain) { + nt_status = normalize_name_map(ctx, domain, user_name, + &mapped_name); + } else { + DEBUG(5,("winbindd_fill_pwent: Failed to find domain for %s. " + "Disabling name alias support\n", dom_name)); + nt_status = NT_STATUS_NO_SUCH_DOMAIN; + } + + /* Basic removal of whitespace */ + if (NT_STATUS_IS_OK(nt_status)) { + fill_domain_username(output_username, dom_name, mapped_name, True); + } + /* Complete name replacement */ + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) { + fstrcpy(output_username, mapped_name); + } + /* No change at all */ + else { + fill_domain_username(output_username, dom_name, user_name, True); + } safe_strcpy(pw->pw_name, output_username, sizeof(pw->pw_name) - 1); @@ -179,6 +203,7 @@ struct getpwsid_state { uid_t uid; DOM_SID group_sid; gid_t gid; + bool username_mapped; }; static void getpwsid_queryuser_recv(void *private_data, bool success, @@ -231,6 +256,8 @@ static void getpwsid_queryuser_recv(void *private_data, bool success, fstring username; struct getpwsid_state *s = talloc_get_type_abort(private_data, struct getpwsid_state); + char *mapped_name; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; if (!success) { DEBUG(5, ("Could not query domain %s SID %s\n", @@ -272,7 +299,23 @@ static void getpwsid_queryuser_recv(void *private_data, bool success, strlower_m( username ); s->username = talloc_strdup(s->state->mem_ctx, username); - ws_name_replace( s->username, WB_REPLACE_CHAR ); + nt_status = normalize_name_map(s->state->mem_ctx, s->domain, + s->username, &mapped_name); + + /* Basic removal of whitespace */ + if (NT_STATUS_IS_OK(nt_status)) { + s->username = mapped_name; + s->username_mapped = false; + } + /* Complete name replacement */ + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) { + s->username = mapped_name; + s->username_mapped = true; + } + /* No change at all */ + else { + s->username_mapped = false; + } s->fullname = talloc_strdup(s->state->mem_ctx, full_name); s->homedir = talloc_strdup(s->state->mem_ctx, homedir); @@ -330,8 +373,16 @@ static void getpwsid_sid2gid_recv(void *private_data, bool success, gid_t gid) pw = &s->state->response.data.pw; pw->pw_uid = s->uid; pw->pw_gid = s->gid; + + /* allow username to be overridden by the alias mapping */ + + if ( s->username_mapped ) { + fstrcpy( output_username, s->username ); + } else { fill_domain_username(output_username, s->domain->name, s->username, True); + } + safe_strcpy(pw->pw_name, output_username, sizeof(pw->pw_name) - 1); safe_strcpy(pw->pw_gecos, s->fullname, sizeof(pw->pw_gecos) - 1); @@ -370,8 +421,10 @@ void winbindd_getpwnam(struct winbindd_cli_state *state) { struct winbindd_domain *domain; fstring domname, username; + char *mapped_user = NULL; char *domuser; size_t dusize; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; domuser = state->request.data.username; dusize = sizeof(state->request.data.username); @@ -383,9 +436,19 @@ void winbindd_getpwnam(struct winbindd_cli_state *state) (unsigned long)state->pid, domuser)); - ws_name_return(domuser, WB_REPLACE_CHAR); + nt_status = normalize_name_unmap(state->mem_ctx, domuser, + &mapped_user); + + /* If we could not convert from an aliased name or a + normalized name, then just use the original name */ + + if (!NT_STATUS_IS_OK(nt_status) && + !NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) + { + mapped_user = domuser; + } - if (!parse_domain_user(domuser, domname, username)) { + if (!parse_domain_user(mapped_user, domname, username)) { DEBUG(5, ("Could not parse domain user: %s\n", domuser)); request_error(state); return; @@ -743,6 +806,7 @@ void winbindd_getpwent(struct winbindd_cli_state *state) /* Lookup user info */ result = winbindd_fill_pwent( + state->mem_ctx, ent->domain_name, name_list[ent->sam_entry_index].name, &name_list[ent->sam_entry_index].user_sid, diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c index 132c96f1ee..fdfc8ed9d1 100644 --- a/source3/winbindd/winbindd_util.c +++ b/source3/winbindd/winbindd_util.c @@ -111,7 +111,16 @@ static struct winbindd_domain *add_trusted_domain(const char *domain_name, const const char *alternative_name = NULL; char *idmap_config_option; const char *param; + const char **ignored_domains, **dom; + 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; + } + } + /* ignore alt_name if we are not in an AD domain */ if ( (lp_security() == SEC_ADS) && alt_name && *alt_name) { @@ -436,6 +445,10 @@ static void rescan_forest_root_trusts( void ) &dom_list[i].sid ); } + if (d == NULL) { + continue; + } + DEBUG(10,("rescan_forest_root_trusts: Following trust path " "for domain tree root %s (%s)\n", d->name, d->alt_name )); @@ -500,6 +513,10 @@ static void rescan_forest_trusts( void ) &cache_methods, &dom_list[i].sid ); } + + if (d == NULL) { + continue; + } DEBUG(10,("Following trust path for domain %s (%s)\n", d->name, d->alt_name )); @@ -1058,13 +1075,12 @@ void free_getent_state(struct getent_state *state) temp = state; while(temp != NULL) { - struct getent_state *next; + struct getent_state *next = temp->next; /* Free sam entries then list entry */ SAFE_FREE(state->sam_entries); DLIST_REMOVE(state, state); - next = temp->next; SAFE_FREE(temp); temp = next; @@ -1160,7 +1176,7 @@ void parse_add_domuser(void *buf, char *domuser, int *len) } } - safe_strcpy(buf, user, *len); + safe_strcpy((char *)buf, user, *len); } /* Ensure an incoming username from NSS is fully qualified. Replace the @@ -1213,6 +1229,33 @@ void fill_domain_username(fstring name, const char *domain, const char *user, bo } } +/** + * talloc version of fill_domain_username() + * return NULL on talloc failure. + */ +char *fill_domain_username_talloc(TALLOC_CTX *mem_ctx, + const char *domain, + const char *user, + bool can_assume) +{ + char *tmp_user, *name; + + tmp_user = talloc_strdup(mem_ctx, user); + strlower_m(tmp_user); + + if (can_assume && assume_domain(domain)) { + name = tmp_user; + } else { + name = talloc_asprintf(mem_ctx, "%s%c%s", + domain, + *lp_winbind_separator(), + tmp_user); + TALLOC_FREE(tmp_user); + } + + return name; +} + /* * Winbindd socket accessor functions */ @@ -1378,34 +1421,107 @@ NTSTATUS lookup_usergroups_cached(struct winbindd_domain *domain, We use this to remove spaces from user and group names ********************************************************************/ -void ws_name_replace( char *name, char replace ) +NTSTATUS normalize_name_map(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + char *name, + char **normalized) { - char replace_char[2] = { 0x0, 0x0 }; - - if ( !lp_winbind_normalize_names() || (replace == '\0') ) - return; + NTSTATUS nt_status; - replace_char[0] = replace; - all_string_sub( name, " ", replace_char, 0 ); + if (!name || !normalized) { + return NT_STATUS_INVALID_PARAMETER; + } - return; + if (!lp_winbind_normalize_names()) { + return NT_STATUS_PROCEDURE_NOT_FOUND; + } + + /* Alias support and whitespace replacement are mutually + exclusive */ + + nt_status = resolve_username_to_alias(mem_ctx, domain, + name, normalized ); + if (NT_STATUS_IS_OK(nt_status)) { + /* special return code to let the caller know we + mapped to an alias */ + return NT_STATUS_FILE_RENAMED; + } + + /* check for an unreachable domain */ + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + DEBUG(5,("normalize_name_map: Setting domain %s offline\n", + domain->name)); + set_domain_offline(domain); + return nt_status; + } + + /* deal with whitespace */ + + *normalized = talloc_strdup(mem_ctx, name); + if (!(*normalized)) { + return NT_STATUS_NO_MEMORY; + } + + all_string_sub( *normalized, " ", "_", 0 ); + + return NT_STATUS_OK; } /********************************************************************* - We use this to do the inverse of ws_name_replace() + We use this to do the inverse of normalize_name_map() ********************************************************************/ -void ws_name_return( char *name, char replace ) +NTSTATUS normalize_name_unmap(TALLOC_CTX *mem_ctx, + char *name, + char **normalized) { - char replace_char[2] = { 0x0, 0x0 }; - - if ( !lp_winbind_normalize_names() || (replace == '\0') ) - return; + NTSTATUS nt_status; + struct winbindd_domain *domain = find_our_domain(); + + if (!name || !normalized) { + return NT_STATUS_INVALID_PARAMETER; + } - replace_char[0] = replace; - all_string_sub( name, replace_char, " ", 0 ); + if (!lp_winbind_normalize_names()) { + return NT_STATUS_PROCEDURE_NOT_FOUND; + } - return; + /* Alias support and whitespace replacement are mutally + exclusive */ + + /* When mapping from an alias to a username, we don't know the + domain. But we only need a domain structure to cache + a successful lookup , so just our own domain structure for + the seqnum. */ + + nt_status = resolve_alias_to_username(mem_ctx, domain, + name, normalized); + if (NT_STATUS_IS_OK(nt_status)) { + /* Special return code to let the caller know we mapped + from an alias */ + return NT_STATUS_FILE_RENAMED; + } + + /* check for an unreachable domain */ + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + DEBUG(5,("normalize_name_unmap: Setting domain %s offline\n", + domain->name)); + set_domain_offline(domain); + return nt_status; + } + + /* deal with whitespace */ + + *normalized = talloc_strdup(mem_ctx, name); + if (!(*normalized)) { + return NT_STATUS_NO_MEMORY; + } + + all_string_sub(*normalized, "_", " ", 0); + + return NT_STATUS_OK; } /********************************************************************* |