diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/winbindd | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/winbindd')
115 files changed, 55286 insertions, 0 deletions
diff --git a/source3/winbindd/idmap.c b/source3/winbindd/idmap.c new file mode 100644 index 0000000..53b860b --- /dev/null +++ b/source3/winbindd/idmap.c @@ -0,0 +1,632 @@ +/* + Unix SMB/CIFS implementation. + ID Mapping + Copyright (C) Tim Potter 2000 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Simo Sorce 2003-2007 + Copyright (C) Jeremy Allison 2006 + Copyright (C) Michael Adam 2010 + + 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.h" +#include "idmap.h" +#include "lib/util_sid_passdb.h" +#include "libcli/security/dom_sid.h" +#include "passdb.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +static_decl_idmap; + +/** + * Pointer to the backend methods. Modules register themselves here via + * smb_register_idmap. + */ + +struct idmap_backend { + const char *name; + const struct idmap_methods *methods; + struct idmap_backend *prev, *next; +}; +static struct idmap_backend *backends = NULL; + +/** + * Default idmap domain configured via "idmap backend". + */ +static struct idmap_domain *default_idmap_domain; + +/** + * Passdb idmap domain, not configurable. winbind must always give passdb a + * chance to map ids. + */ +static struct idmap_domain *passdb_idmap_domain; + +/** + * List of specially configured idmap domains. This list is filled on demand + * in the winbind idmap child when the parent winbind figures out via the + * special range parameter or via the domain SID that a special "idmap config + * domain" configuration is present. + */ +static struct idmap_domain **idmap_domains = NULL; +static int num_domains = 0; + +static struct idmap_domain *idmap_init_named_domain(TALLOC_CTX *mem_ctx, + const char *domname); +static struct idmap_domain *idmap_init_domain(TALLOC_CTX *mem_ctx, + const char *domainname, + const char *modulename, + bool check_range); + +struct lp_scan_idmap_domains_state { + bool (*fn)(const char *domname, void *private_data); + void *private_data; +}; + +static bool lp_scan_idmap_found_domain( + const char *string, regmatch_t matches[], void *private_data); + +bool lp_scan_idmap_domains(bool (*fn)(const char *domname, + void *private_data), + void *private_data) +{ + struct lp_scan_idmap_domains_state state = { + .fn = fn, .private_data = private_data }; + int ret; + + ret = lp_wi_scan_global_parametrics( + "idmapconfig\\(.*\\):backend", 2, + lp_scan_idmap_found_domain, &state); + if (ret != 0) { + DBG_WARNING("wi_scan_global_parametrics returned %d\n", ret); + return false; + } + + return true; +} + +static bool lp_scan_idmap_found_domain( + const char *string, regmatch_t matches[], void *private_data) +{ + bool ok; + + if (matches[1].rm_so == -1) { + DBG_WARNING("Found match, but no name??\n"); + return false; + } + if (matches[1].rm_eo <= matches[1].rm_so) { + DBG_WARNING("Invalid match\n"); + return false; + } + + { + struct lp_scan_idmap_domains_state *state = private_data; + regoff_t len = matches[1].rm_eo - matches[1].rm_so; + char domname[len+1]; + + memcpy(domname, string + matches[1].rm_so, len); + domname[len] = '\0'; + + DBG_DEBUG("Found idmap domain \"%s\"\n", domname); + + ok = state->fn(domname, state->private_data); + } + + return ok; +} + +static bool idmap_found_domain_backend(const char *domname, + void *private_data); + +static bool idmap_init(void) +{ + static bool initialized; + bool ok; + + if (initialized) { + return true; + } + + DEBUG(10, ("idmap_init(): calling static_init_idmap\n")); + + static_init_idmap(NULL); + + initialized = true; + + if (!pdb_is_responsible_for_everything_else()) { + default_idmap_domain = idmap_init_named_domain(NULL, "*"); + if (default_idmap_domain == NULL) { + return false; + } + } + + passdb_idmap_domain = idmap_init_domain( + NULL, get_global_sam_name(), "passdb", false); + if (passdb_idmap_domain == NULL) { + TALLOC_FREE(default_idmap_domain); + return false; + } + + idmap_domains = talloc_array(NULL, struct idmap_domain *, 0); + if (idmap_domains == NULL) { + TALLOC_FREE(passdb_idmap_domain); + TALLOC_FREE(default_idmap_domain); + return false; + } + + ok = lp_scan_idmap_domains(idmap_found_domain_backend, NULL); + if (!ok) { + DBG_WARNING("lp_scan_idmap_domains failed\n"); + return false; + } + + return true; +} + +static int idmap_config_name(const char *domname, char *buf, size_t buflen) +{ + int len = snprintf(buf, buflen, "idmap config %s", domname); + SMB_ASSERT(len > 0); + return len + 1; +} + +const char *idmap_config_const_string(const char *domname, const char *option, + const char *def) +{ + int len = idmap_config_name(domname, NULL, 0); + char config_option[len]; + idmap_config_name(domname, config_option, sizeof(config_option)); + + return lp_parm_const_string(-1, config_option, option, def); +} + +bool idmap_config_bool(const char *domname, const char *option, bool def) +{ + int len = idmap_config_name(domname, NULL, 0); + char config_option[len]; + idmap_config_name(domname, config_option, sizeof(config_option)); + + return lp_parm_bool(-1, config_option, option, def); +} + +int idmap_config_int(const char *domname, const char *option, int def) +{ + int len = idmap_config_name(domname, NULL, 0); + char config_option[len]; + idmap_config_name(domname, config_option, sizeof(config_option)); + + return lp_parm_int(-1, config_option, option, def); +} + +const char **idmap_config_string_list(const char *domname, + const char *option, + const char **def) +{ + int len = idmap_config_name(domname, NULL, 0); + char config_option[len]; + idmap_config_name(domname, config_option, sizeof(config_option)); + + return lp_parm_string_list(-1, config_option, option, def); +} + +bool domain_has_idmap_config(const char *domname) +{ + int i; + const char *range = NULL; + const char *backend = NULL; + bool ok; + + ok = idmap_init(); + if (!ok) { + return false; + } + + for (i=0; i<num_domains; i++) { + if (strequal(idmap_domains[i]->name, domname)) { + return true; + } + } + + /* fallback: also check loadparm */ + + range = idmap_config_const_string(domname, "range", NULL); + backend = idmap_config_const_string(domname, "backend", NULL); + if (range != NULL && backend != NULL) { + DEBUG(5, ("idmap configuration specified for domain '%s'\n", + domname)); + return true; + } + + return false; +} + +static bool idmap_found_domain_backend(const char *domname, + void *private_data) +{ + struct idmap_domain *dom, **tmp; + + DBG_DEBUG("Found idmap domain \"%s\"\n", domname); + + if (strcmp(domname, "*") == 0) { + return false; + } + + dom = idmap_init_named_domain(idmap_domains, domname); + if (dom == NULL) { + DBG_NOTICE("Could not init idmap domain %s\n", domname); + return false; + } + + tmp = talloc_realloc(idmap_domains, idmap_domains, + struct idmap_domain *, num_domains + 1); + if (tmp == NULL) { + DBG_WARNING("talloc_realloc failed\n"); + TALLOC_FREE(dom); + return false; + } + idmap_domains = tmp; + idmap_domains[num_domains] = dom; + num_domains += 1; + + return false; +} + +static const struct idmap_methods *get_methods(const char *name) +{ + struct idmap_backend *b; + + for (b = backends; b; b = b->next) { + if (strequal(b->name, name)) { + return b->methods; + } + } + + return NULL; +} + +bool idmap_is_offline(void) +{ + return ( lp_winbind_offline_logon() && + get_global_winbindd_state_offline() ); +} + +/********************************************************************** + Allow a module to register itself as a method. +**********************************************************************/ + +NTSTATUS smb_register_idmap(int version, const char *name, + const struct idmap_methods *methods) +{ + struct idmap_backend *entry; + + if ((version != SMB_IDMAP_INTERFACE_VERSION)) { + DEBUG(0, ("Failed to register idmap module.\n" + "The module was compiled against " + "SMB_IDMAP_INTERFACE_VERSION %d,\n" + "current SMB_IDMAP_INTERFACE_VERSION is %d.\n" + "Please recompile against the current version " + "of samba!\n", + version, SMB_IDMAP_INTERFACE_VERSION)); + return NT_STATUS_OBJECT_TYPE_MISMATCH; + } + + if (!name || !name[0] || !methods) { + DEBUG(0,("Called with NULL pointer or empty name!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + for (entry = backends; entry != NULL; entry = entry->next) { + if (strequal(entry->name, name)) { + DEBUG(5,("Idmap module %s already registered!\n", + name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + } + + entry = talloc(NULL, struct idmap_backend); + if ( ! entry) { + DEBUG(0,("Out of memory!\n")); + TALLOC_FREE(entry); + return NT_STATUS_NO_MEMORY; + } + entry->name = talloc_strdup(entry, name); + if ( ! entry->name) { + DEBUG(0,("Out of memory!\n")); + TALLOC_FREE(entry); + return NT_STATUS_NO_MEMORY; + } + entry->methods = methods; + + DLIST_ADD(backends, entry); + DEBUG(5, ("Successfully added idmap backend '%s'\n", name)); + return NT_STATUS_OK; +} + +/** + * Initialize a domain structure + * @param[in] mem_ctx memory context for the result + * @param[in] domainname which domain is this for + * @param[in] modulename which backend module + * @param[in] check_range whether range checking should be done + * @result The initialized structure + */ +static struct idmap_domain *idmap_init_domain(TALLOC_CTX *mem_ctx, + const char *domainname, + const char *modulename, + bool check_range) +{ + struct idmap_domain *result; + NTSTATUS status; + const char *range; + unsigned low_id = 0; + unsigned high_id = 0; + + result = talloc_zero(mem_ctx, struct idmap_domain); + if (result == NULL) { + DEBUG(0, ("talloc failed\n")); + return NULL; + } + + result->name = talloc_strdup(result, domainname); + if (result->name == NULL) { + DEBUG(0, ("talloc failed\n")); + goto fail; + } + + /* + * Check whether the requested backend module exists and + * load the methods. + */ + + result->methods = get_methods(modulename); + if (result->methods == NULL) { + DEBUG(3, ("idmap backend %s not found\n", modulename)); + + status = smb_probe_module("idmap", modulename); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not probe idmap module %s\n", + modulename)); + goto fail; + } + + result->methods = get_methods(modulename); + } + if (result->methods == NULL) { + DEBUG(1, ("idmap backend %s not found\n", modulename)); + goto fail; + } + + /* + * load ranges and read only information from the config + */ + + result->read_only = idmap_config_bool(result->name, "read only", false); + range = idmap_config_const_string(result->name, "range", NULL); + + if (range == NULL) { + if (check_range) { + DEBUG(1, ("idmap range not specified for domain %s\n", + result->name)); + goto fail; + } + } else if (sscanf(range, "%u - %u", &low_id, &high_id) != 2) + { + DEBUG(1, ("invalid range '%s' specified for domain " + "'%s'\n", range, result->name)); + if (check_range) { + goto fail; + } + } else if (low_id > high_id) { + DEBUG(1, ("Error: invalid idmap range detected: %u - %u\n", + low_id, high_id)); + if (check_range) { + goto fail; + } + } + + result->low_id = low_id; + result->high_id = high_id; + + status = result->methods->init(result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("idmap initialization returned %s\n", + nt_errstr(status))); + goto fail; + } + + return result; + +fail: + TALLOC_FREE(result); + return NULL; +} + +/** + * Initialize a named domain structure + * @param[in] mem_ctx memory context for the result + * @param[in] domname the domain name + * @result The default domain structure + * + * This routine looks at the "idmap config <domname>" parameters to figure out + * the configuration. + */ + +static struct idmap_domain *idmap_init_named_domain(TALLOC_CTX *mem_ctx, + const char *domname) +{ + struct idmap_domain *result = NULL; + const char *backend; + bool ok; + + ok = idmap_init(); + if (!ok) { + return NULL; + } + + backend = idmap_config_const_string(domname, "backend", NULL); + if (backend == NULL) { + DEBUG(10, ("no idmap backend configured for domain '%s'\n", + domname)); + goto fail; + } + + result = idmap_init_domain(mem_ctx, domname, backend, true); + if (result == NULL) { + goto fail; + } + + return result; + +fail: + TALLOC_FREE(result); + return NULL; +} + +/** + * Find a domain struct according to a domain name + * @param[in] domname Domain name to get the config for + * @result The default domain structure that fits + * + * This is the central routine in the winbindd-idmap child to pick the correct + * domain for looking up IDs. If domname is NULL or empty, we use the default + * domain. If it contains something, we try to use idmap_init_named_domain() + * to fetch the correct backend. + * + * The choice about "domname" is being made by the winbind parent, look at the + * "have_idmap_config" of "struct winbindd_domain" which is set in + * add_trusted_domain. + */ + +struct idmap_domain *idmap_find_domain(const char *domname) +{ + bool ok; + int i; + + DEBUG(10, ("idmap_find_domain called for domain '%s'\n", + domname?domname:"NULL")); + + ok = idmap_init(); + if (!ok) { + return NULL; + } + + if ((domname == NULL) || (domname[0] == '\0')) { + return default_idmap_domain; + } + + for (i=0; i<num_domains; i++) { + if (strequal(idmap_domains[i]->name, domname)) { + return idmap_domains[i]; + } + } + + return default_idmap_domain; +} + +struct idmap_domain *idmap_find_domain_with_sid(const char *domname, + const struct dom_sid *sid) +{ + bool ok; + + ok = idmap_init(); + if (!ok) { + return NULL; + } + + if (sid_check_is_for_passdb(sid)) { + return passdb_idmap_domain; + } + + return idmap_find_domain(domname); +} + +void idmap_close(void) +{ + TALLOC_FREE(default_idmap_domain); + TALLOC_FREE(passdb_idmap_domain); + TALLOC_FREE(idmap_domains); + num_domains = 0; +} + +/************************************************************************** + idmap allocator interface functions +**************************************************************************/ + +static NTSTATUS idmap_allocate_unixid(struct unixid *id) +{ + struct idmap_domain *dom; + NTSTATUS ret; + + dom = idmap_find_domain(NULL); + + if (dom == NULL) { + return NT_STATUS_UNSUCCESSFUL; + } + + if (dom->methods->allocate_id == NULL) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + ret = dom->methods->allocate_id(dom, id); + + return ret; +} + + +NTSTATUS idmap_allocate_uid(struct unixid *id) +{ + id->type = ID_TYPE_UID; + return idmap_allocate_unixid(id); +} + +NTSTATUS idmap_allocate_gid(struct unixid *id) +{ + id->type = ID_TYPE_GID; + return idmap_allocate_unixid(id); +} + +NTSTATUS idmap_backend_unixids_to_sids(struct id_map **maps, + const char *domain_name, + struct dom_sid domain_sid) +{ + struct idmap_domain *dom = NULL; + NTSTATUS status; + bool ok; + + ok = idmap_init(); + if (!ok) { + return NT_STATUS_NONE_MAPPED; + } + + if (strequal(domain_name, get_global_sam_name())) { + dom = passdb_idmap_domain; + } + if (dom == NULL) { + dom = idmap_find_domain(domain_name); + } + if (dom == NULL) { + return NT_STATUS_NONE_MAPPED; + } + + dom->dom_sid = domain_sid; + status = dom->methods->unixids_to_sids(dom, maps); + + DBG_DEBUG("unixid_to_sids for domain %s returned %s\n", + domain_name, nt_errstr(status)); + + return status; +} diff --git a/source3/winbindd/idmap_ad.c b/source3/winbindd/idmap_ad.c new file mode 100644 index 0000000..5c9fe07 --- /dev/null +++ b/source3/winbindd/idmap_ad.c @@ -0,0 +1,1243 @@ +/* + * idmap_ad: map between Active Directory and RFC 2307 accounts + * + * Copyright (C) Volker Lendecke 2015 + * + * 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.h" +#include "libsmb/namequery.h" +#include "idmap.h" +#include "tldap_gensec_bind.h" +#include "tldap_util.h" +#include "passdb.h" +#include "lib/param/param.h" +#include "auth/gensec/gensec.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "libads/ldap_schema_oids.h" +#include "../libds/common/flags.h" +#include "libcli/ldap/ldap_ndr.h" +#include "libcli/security/dom_sid.h" +#include "source3/libads/sitename_cache.h" +#include "source3/libads/kerberos_proto.h" +#include "source3/librpc/gen_ndr/ads.h" +#include "source3/lib/global_contexts.h" +#include <ldb.h> + +struct idmap_ad_schema_names; + +struct idmap_ad_context { + struct idmap_domain *dom; + struct tldap_context *ld; + struct idmap_ad_schema_names *schema; + const char *default_nc; + + bool unix_primary_group; + bool unix_nss_info; + + struct ldb_context *ldb; + struct ldb_dn **deny_ous; + struct ldb_dn **allow_ous; +}; + +static NTSTATUS idmap_ad_get_context(struct idmap_domain *dom, + struct idmap_ad_context **pctx); + +static char *get_schema_path(TALLOC_CTX *mem_ctx, struct tldap_context *ld) +{ + struct tldap_message *rootdse; + + rootdse = tldap_rootdse(ld); + if (rootdse == NULL) { + return NULL; + } + + return tldap_talloc_single_attribute(rootdse, "schemaNamingContext", + mem_ctx); +} + +static char *get_default_nc(TALLOC_CTX *mem_ctx, struct tldap_context *ld) +{ + struct tldap_message *rootdse; + + rootdse = tldap_rootdse(ld); + if (rootdse == NULL) { + return NULL; + } + + return tldap_talloc_single_attribute(rootdse, "defaultNamingContext", + mem_ctx); +} + +struct idmap_ad_schema_names { + char *name; + char *uid; + char *gid; + char *gecos; + char *dir; + char *shell; +}; + +static TLDAPRC get_attrnames_by_oids(struct tldap_context *ld, + TALLOC_CTX *mem_ctx, + const char *schema_path, + size_t num_oids, + const char **oids, + char **names) +{ + char *filter; + const char *attrs[] = { "lDAPDisplayName", "attributeId" }; + size_t i; + TLDAPRC rc; + struct tldap_message **msgs; + size_t num_msgs; + + filter = talloc_strdup(mem_ctx, "(|"); + + for (i=0; i<num_oids; i++) { + talloc_asprintf_addbuf(&filter, "(attributeId=%s)", oids[i]); + } + talloc_asprintf_addbuf(&filter, ")"); + + if (filter == NULL) { + return TLDAP_NO_MEMORY; + } + + rc = tldap_search(ld, schema_path, TLDAP_SCOPE_SUB, filter, + attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0, + 0, 0, 0, mem_ctx, &msgs);; + TALLOC_FREE(filter); + if (!TLDAP_RC_IS_SUCCESS(rc)) { + return rc; + } + + for (i=0; i<num_oids; i++) { + names[i] = NULL; + } + + num_msgs = talloc_array_length(msgs); + + for (i=0; i<num_msgs; i++) { + struct tldap_message *msg = msgs[i]; + char *oid; + size_t j; + + if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) { + /* Could be a TLDAP_RES_SEARCH_REFERENCE */ + continue; + } + + oid = tldap_talloc_single_attribute( + msg, "attributeId", msg); + if (oid == NULL) { + continue; + } + + for (j=0; j<num_oids; j++) { + if (strequal(oid, oids[j])) { + break; + } + } + TALLOC_FREE(oid); + + if (j == num_oids) { + /* not found */ + continue; + } + + names[j] = tldap_talloc_single_attribute( + msg, "lDAPDisplayName", mem_ctx); + } + + TALLOC_FREE(msgs); + for (i=0; i<num_oids; i++) { + if (names[i] == NULL) { + DBG_ERR("Failed to retrieve schema name for " + "oid [%s]. Schema mode is incorrect " + "for this domain.\n", oids[i]); + return TLDAP_FILTER_ERROR; + } + } + + return TLDAP_SUCCESS; +} + +static TLDAPRC get_posix_schema_names(struct tldap_context *ld, + const char *schema_mode, + TALLOC_CTX *mem_ctx, + struct idmap_ad_schema_names **pschema) +{ + char *schema_path; + struct idmap_ad_schema_names *schema; + char *names[6]; + const char *oids_sfu[] = { + ADS_ATTR_SFU_UIDNUMBER_OID, + ADS_ATTR_SFU_GIDNUMBER_OID, + ADS_ATTR_SFU_HOMEDIR_OID, + ADS_ATTR_SFU_SHELL_OID, + ADS_ATTR_SFU_GECOS_OID, + ADS_ATTR_SFU_UID_OID + }; + const char *oids_sfu20[] = { + ADS_ATTR_SFU20_UIDNUMBER_OID, + ADS_ATTR_SFU20_GIDNUMBER_OID, + ADS_ATTR_SFU20_HOMEDIR_OID, + ADS_ATTR_SFU20_SHELL_OID, + ADS_ATTR_SFU20_GECOS_OID, + ADS_ATTR_SFU20_UID_OID + }; + const char *oids_rfc2307[] = { + ADS_ATTR_RFC2307_UIDNUMBER_OID, + ADS_ATTR_RFC2307_GIDNUMBER_OID, + ADS_ATTR_RFC2307_HOMEDIR_OID, + ADS_ATTR_RFC2307_SHELL_OID, + ADS_ATTR_RFC2307_GECOS_OID, + ADS_ATTR_RFC2307_UID_OID + }; + const char **oids; + + TLDAPRC rc; + + schema = talloc(mem_ctx, struct idmap_ad_schema_names); + if (schema == NULL) { + return TLDAP_NO_MEMORY; + } + + schema_path = get_schema_path(schema, ld); + if (schema_path == NULL) { + TALLOC_FREE(schema); + return TLDAP_NO_MEMORY; + } + + oids = oids_rfc2307; + + if ((schema_mode != NULL) && (schema_mode[0] != '\0')) { + if (strequal(schema_mode, "sfu")) { + oids = oids_sfu; + } else if (strequal(schema_mode, "sfu20")) { + oids = oids_sfu20; + } else if (strequal(schema_mode, "rfc2307" )) { + oids = oids_rfc2307; + } else { + DBG_WARNING("Unknown schema mode %s\n", schema_mode); + } + } + + rc = get_attrnames_by_oids(ld, schema, schema_path, 6, oids, names); + TALLOC_FREE(schema_path); + if (!TLDAP_RC_IS_SUCCESS(rc)) { + TALLOC_FREE(schema); + return rc; + } + + schema->uid = names[0]; + schema->gid = names[1]; + schema->dir = names[2]; + schema->shell = names[3]; + schema->gecos = names[4]; + schema->name = names[5]; + + *pschema = schema; + + return TLDAP_SUCCESS; +} + +static void PRINTF_ATTRIBUTE(3, 0) idmap_ad_tldap_debug( + void *log_private, + enum tldap_debug_level level, + const char *fmt, + va_list ap) +{ + int samba_level = -1; + + switch (level) { + case TLDAP_DEBUG_FATAL: + samba_level = DBGLVL_ERR; + break; + case TLDAP_DEBUG_ERROR: + samba_level = DBGLVL_ERR; + break; + case TLDAP_DEBUG_WARNING: + samba_level = DBGLVL_WARNING; + break; + case TLDAP_DEBUG_TRACE: + samba_level = DBGLVL_DEBUG; + break; + } + + if (CHECK_DEBUGLVL(samba_level)) { + char *s = NULL; + int ret; + + ret = vasprintf(&s, fmt, ap); + if (ret == -1) { + return; + } + DEBUG(samba_level, ("idmap_ad_tldap: %s", s)); + free(s); + } +} + +static uint32_t gensec_features_from_ldap_sasl_wrapping(void) +{ + int wrap_flags; + uint32_t gensec_features = 0; + + wrap_flags = lp_client_ldap_sasl_wrapping(); + if (wrap_flags == -1) { + wrap_flags = 0; + } + + if (wrap_flags & ADS_AUTH_SASL_SEAL) { + gensec_features |= GENSEC_FEATURE_SEAL; + } + if (wrap_flags & ADS_AUTH_SASL_SIGN) { + gensec_features |= GENSEC_FEATURE_SIGN; + } + + if (gensec_features != 0) { + gensec_features |= GENSEC_FEATURE_LDAP_STYLE; + } + + return gensec_features; +} + +static NTSTATUS idmap_ad_get_tldap_ctx(TALLOC_CTX *mem_ctx, + const char *domname, + struct tldap_context **pld) +{ + struct netr_DsRGetDCNameInfo *dcinfo; + struct sockaddr_storage dcaddr; + struct cli_credentials *creds; + struct loadparm_context *lp_ctx; + struct tldap_context *ld; + uint32_t gensec_features = gensec_features_from_ldap_sasl_wrapping(); + char *sitename = NULL; + int fd; + NTSTATUS status; + bool ok; + TLDAPRC rc; + + status = wb_dsgetdcname_gencache_get(mem_ctx, domname, &dcinfo); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("Could not get dcinfo for %s: %s\n", domname, + nt_errstr(status)); + return status; + } + + if (dcinfo->dc_unc == NULL) { + TALLOC_FREE(dcinfo); + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + } + if (dcinfo->dc_unc[0] == '\\') { + dcinfo->dc_unc += 1; + } + if (dcinfo->dc_unc[0] == '\\') { + dcinfo->dc_unc += 1; + } + + ok = resolve_name(dcinfo->dc_unc, &dcaddr, 0x20, true); + if (!ok) { + DBG_DEBUG("Could not resolve name %s\n", dcinfo->dc_unc); + TALLOC_FREE(dcinfo); + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + } + + sitename = sitename_fetch(talloc_tos(), lp_realm()); + + /* + * create_local_private_krb5_conf_for_domain() can deal with + * sitename==NULL + */ + + ok = create_local_private_krb5_conf_for_domain( + lp_realm(), lp_workgroup(), sitename, &dcaddr); + TALLOC_FREE(sitename); + if (!ok) { + DBG_DEBUG("Could not create private krb5.conf\n"); + TALLOC_FREE(dcinfo); + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + } + + status = open_socket_out(&dcaddr, 389, 10000, &fd); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("open_socket_out failed: %s\n", nt_errstr(status)); + TALLOC_FREE(dcinfo); + return status; + } + + ld = tldap_context_create(dcinfo, fd); + if (ld == NULL) { + DBG_DEBUG("tldap_context_create failed\n"); + close(fd); + TALLOC_FREE(dcinfo); + return NT_STATUS_NO_MEMORY; + } + tldap_set_debug(ld, idmap_ad_tldap_debug, NULL); + + /* + * Here we use or own machine account as + * we run as domain member. + */ + status = pdb_get_trust_credentials(lp_workgroup(), + lp_realm(), + dcinfo, + &creds); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("pdb_get_trust_credentials() failed - %s\n", + nt_errstr(status)); + TALLOC_FREE(dcinfo); + return status; + } + + lp_ctx = loadparm_init_s3(dcinfo, loadparm_s3_helpers()); + if (lp_ctx == NULL) { + DBG_DEBUG("loadparm_init_s3 failed\n"); + TALLOC_FREE(dcinfo); + return NT_STATUS_NO_MEMORY; + } + + rc = tldap_gensec_bind(ld, creds, "ldap", dcinfo->dc_unc, NULL, lp_ctx, + gensec_features); + if (!TLDAP_RC_IS_SUCCESS(rc)) { + DBG_DEBUG("tldap_gensec_bind failed: %s\n", + tldap_errstr(dcinfo, ld, rc)); + TALLOC_FREE(dcinfo); + return NT_STATUS_LDAP(TLDAP_RC_V(rc)); + } + + rc = tldap_fetch_rootdse(ld); + if (!TLDAP_RC_IS_SUCCESS(rc)) { + DBG_DEBUG("tldap_fetch_rootdse failed: %s\n", + tldap_errstr(dcinfo, ld, rc)); + TALLOC_FREE(dcinfo); + return NT_STATUS_LDAP(TLDAP_RC_V(rc)); + } + + *pld = talloc_move(mem_ctx, &ld); + TALLOC_FREE(dcinfo); + return NT_STATUS_OK; +} + +static int idmap_ad_context_destructor(struct idmap_ad_context *ctx) +{ + if ((ctx->dom != NULL) && (ctx->dom->private_data == ctx)) { + ctx->dom->private_data = NULL; + } + return 0; +} + +static struct ldb_dn **str_list_to_dns(TALLOC_CTX *mem_ctx, + const char *dbgmsg, + struct ldb_context *ldb, + const char **strlist) +{ + size_t i, num_dns = str_list_length(strlist); + char *dbgstr = NULL; + struct ldb_dn **dns = NULL; + + dns = talloc_array(mem_ctx, struct ldb_dn *, num_dns); + if (dns == NULL) { + TALLOC_FREE(dbgstr); + return NULL; + } + + dbgstr = talloc_strdup(talloc_tos(), ""); + + for (i = 0; i < num_dns; i++) { + dns[i] = ldb_dn_new(dns, ldb, strlist[i]); + if (dns[i] == NULL) { + DBG_WARNING("ldb_dn_new(%s) failed\n", strlist[i]); + TALLOC_FREE(dns); + return NULL; + } + talloc_asprintf_addbuf( + &dbgstr, + "%s ", + ldb_dn_get_extended_linearized(dbgstr, dns[i], 1)); + } + + DBG_DEBUG("%s %s\n", dbgmsg, dbgstr); + TALLOC_FREE(dbgstr); + + return dns; +} + +static NTSTATUS idmap_ad_context_create(TALLOC_CTX *mem_ctx, + struct idmap_domain *dom, + const char *domname, + struct idmap_ad_context **pctx) +{ + struct idmap_ad_context *ctx; + const char *schema_mode; + const char **allow = NULL; + const char **deny = NULL; + NTSTATUS status; + TLDAPRC rc; + + ctx = talloc_zero(mem_ctx, struct idmap_ad_context); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + ctx->dom = dom; + + talloc_set_destructor(ctx, idmap_ad_context_destructor); + + status = idmap_ad_get_tldap_ctx(ctx, domname, &ctx->ld); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("idmap_ad_get_tldap_ctx failed: %s\n", + nt_errstr(status)); + TALLOC_FREE(ctx); + return status; + } + + ctx->default_nc = get_default_nc(ctx, ctx->ld); + if (ctx->default_nc == NULL) { + DBG_DEBUG("No default nc\n"); + TALLOC_FREE(ctx); + return status; + } + + ctx->unix_primary_group = idmap_config_bool( + domname, "unix_primary_group", false); + ctx->unix_nss_info = idmap_config_bool( + domname, "unix_nss_info", false); + + schema_mode = idmap_config_const_string( + domname, "schema_mode", "rfc2307"); + + rc = get_posix_schema_names(ctx->ld, schema_mode, ctx, &ctx->schema); + if (!TLDAP_RC_IS_SUCCESS(rc)) { + DBG_DEBUG("get_posix_schema_names failed: %s\n", + tldap_errstr(ctx, ctx->ld, rc)); + TALLOC_FREE(ctx); + return NT_STATUS_LDAP(TLDAP_RC_V(rc)); + } + + deny = idmap_config_string_list(domname, "deny ous", NULL); + allow = idmap_config_string_list(domname, "allow ous", NULL); + + if ((deny != NULL) || (allow != NULL)) { + int ret = ldb_global_init(); + if (ret == -1) { + status = map_nt_error_from_unix(errno); + DBG_WARNING("ldb_global_init() failed: %s\n", + strerror(errno)); + TALLOC_FREE(ctx); + return status; + } + + ctx->ldb = ldb_init(ctx, global_event_context()); + if (ctx->ldb == NULL) { + status = map_nt_error_from_unix(errno); + DBG_WARNING("ldb_init() failed: %s\n", strerror(errno)); + TALLOC_FREE(ctx); + return status; + } + } + + if (deny != NULL) { + ctx->deny_ous = str_list_to_dns(ctx, "Denying", ctx->ldb, deny); + if (ctx->deny_ous == NULL) { + DBG_DEBUG("str_list_to_dns failed\n"); + TALLOC_FREE(ctx); + return NT_STATUS_NO_MEMORY; + } + } + + if (allow != NULL) { + ctx->allow_ous = + str_list_to_dns(ctx, "Allowing", ctx->ldb, allow); + if (ctx->allow_ous == NULL) { + DBG_DEBUG("str_list_to_dns failed\n"); + TALLOC_FREE(ctx); + return NT_STATUS_NO_MEMORY; + } + } + + *pctx = ctx; + return NT_STATUS_OK; +} + +static bool check_dn(struct ldb_dn **dns, struct ldb_dn *dn) +{ + size_t i, num_dns = talloc_array_length(dns); + + for (i = 0; i < num_dns; i++) { + struct ldb_dn *base = dns[i]; + int ret = ldb_dn_compare_base(base, dn); + if (ret == 0) { + return true; + } + } + return false; +} + +static bool idmap_ad_dn_filter(struct idmap_domain *dom, const char *dnstr) +{ + struct idmap_ad_context *ctx = NULL; + struct ldb_dn *dn = NULL; + NTSTATUS status; + bool result = false; + + status = idmap_ad_get_context(dom, &ctx); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("idmap_ad_get_context failed: %s\n", + nt_errstr(status)); + return false; + } + + if ((ctx->allow_ous == NULL) && (ctx->deny_ous == NULL)) { + /* + * Nothing to check + */ + return true; + } + + dn = ldb_dn_new(talloc_tos(), ctx->ldb, dnstr); + if (dn == NULL) { + DBG_DEBUG("ldb_dn_new(%s) failed\n", dnstr); + return false; + } + + if (ctx->deny_ous != NULL) { + bool denied = check_dn(ctx->deny_ous, dn); + if (denied) { + DBG_WARNING("Denied %s\n", dnstr); + TALLOC_FREE(dn); + return false; + } + + if (ctx->allow_ous == NULL) { + /* + * Only a few denied OUs around, allow by + * default + */ + result = true; + } + } + + if (ctx->allow_ous != NULL) { + bool allowed = check_dn(ctx->allow_ous, dn); + if (allowed) { + return true; + } + DBG_WARNING("Did not allow %s\n", dnstr); + } + + return result; +} + +static NTSTATUS idmap_ad_query_user(struct idmap_domain *domain, + struct wbint_userinfo *info) +{ + struct idmap_ad_context *ctx; + TLDAPRC rc; + NTSTATUS status; + char *sidstr, *filter; + const char *attrs[4]; + size_t i, num_msgs; + struct tldap_message **msgs; + + status = idmap_ad_get_context(domain, &ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!(ctx->unix_primary_group || ctx->unix_nss_info)) { + return NT_STATUS_OK; + } + + attrs[0] = ctx->schema->gid; + attrs[1] = ctx->schema->gecos; + attrs[2] = ctx->schema->dir; + attrs[3] = ctx->schema->shell; + + sidstr = ldap_encode_ndr_dom_sid(talloc_tos(), &info->user_sid); + if (sidstr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(talloc_tos(), "(objectsid=%s)", sidstr); + TALLOC_FREE(sidstr); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("Filter: [%s]\n", filter); + + rc = tldap_search(ctx->ld, ctx->default_nc, TLDAP_SCOPE_SUB, filter, + attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0, + 0, 0, 0, talloc_tos(), &msgs); + if (!TLDAP_RC_IS_SUCCESS(rc)) { + return NT_STATUS_LDAP(TLDAP_RC_V(rc)); + } + + TALLOC_FREE(filter); + + num_msgs = talloc_array_length(msgs); + + for (i=0; i<num_msgs; i++) { + struct tldap_message *msg = msgs[i]; + char *dn = NULL; + bool ok; + + if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) { + continue; + } + ok = tldap_entry_dn(msg, &dn); + if (!ok) { + continue; + } + ok = idmap_ad_dn_filter(domain, dn); + if (!ok) { + DBG_DEBUG("%s filtered out\n", dn); + continue; + } + + if (ctx->unix_primary_group) { + uint32_t gid; + + ok = tldap_pull_uint32(msg, ctx->schema->gid, &gid); + if (ok) { + DBG_DEBUG("Setting primary group " + "to %"PRIu32" from attr %s\n", + gid, ctx->schema->gid); + info->primary_gid = gid; + } + } + + if (ctx->unix_nss_info) { + char *attr; + + attr = tldap_talloc_single_attribute( + msg, ctx->schema->dir, talloc_tos()); + if (attr != NULL) { + info->homedir = talloc_move(info, &attr); + } + TALLOC_FREE(attr); + + attr = tldap_talloc_single_attribute( + msg, ctx->schema->shell, talloc_tos()); + if (attr != NULL) { + info->shell = talloc_move(info, &attr); + } + TALLOC_FREE(attr); + + attr = tldap_talloc_single_attribute( + msg, ctx->schema->gecos, talloc_tos()); + if (attr != NULL) { + info->full_name = talloc_move(info, &attr); + } + TALLOC_FREE(attr); + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS idmap_ad_query_user_retry(struct idmap_domain *domain, + struct wbint_userinfo *info) +{ + const NTSTATUS status_server_down = + NT_STATUS_LDAP(TLDAP_RC_V(TLDAP_SERVER_DOWN)); + NTSTATUS status; + + status = idmap_ad_query_user(domain, info); + + if (NT_STATUS_EQUAL(status, status_server_down)) { + TALLOC_FREE(domain->private_data); + status = idmap_ad_query_user(domain, info); + } + + return status; +} + +static NTSTATUS idmap_ad_initialize(struct idmap_domain *dom) +{ + dom->query_user = idmap_ad_query_user_retry; + dom->private_data = NULL; + return NT_STATUS_OK; +} + +static NTSTATUS idmap_ad_get_context(struct idmap_domain *dom, + struct idmap_ad_context **pctx) +{ + struct idmap_ad_context *ctx = NULL; + NTSTATUS status; + + if (IS_AD_DC) { + /* + * Make sure we never try to use LDAP against + * a trusted domain as AD_DC. + * + * This shouldn't be called currently, + * but you never know what happens in future. + */ + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + if (dom->private_data != NULL) { + *pctx = talloc_get_type_abort(dom->private_data, + struct idmap_ad_context); + return NT_STATUS_OK; + } + + status = idmap_ad_context_create(dom, dom, dom->name, &ctx); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("idmap_ad_context_create failed: %s\n", + nt_errstr(status)); + return status; + } + + dom->private_data = ctx; + *pctx = ctx; + return NT_STATUS_OK; +} + +static NTSTATUS idmap_ad_unixids_to_sids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct idmap_ad_context *ctx; + TLDAPRC rc; + NTSTATUS status; + struct tldap_message **msgs; + + size_t i, num_msgs; + char *u_filter, *g_filter, *filter; + + const char *attrs[] = { + "sAMAccountType", + "objectSid", + NULL, /* attr_uidnumber */ + NULL, /* attr_gidnumber */ + }; + + status = idmap_ad_get_context(dom, &ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + attrs[2] = ctx->schema->uid; + attrs[3] = ctx->schema->gid; + + u_filter = talloc_strdup(talloc_tos(), ""); + if (u_filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + g_filter = talloc_strdup(talloc_tos(), ""); + if (g_filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; ids[i] != NULL; i++) { + struct id_map *id = ids[i]; + + id->status = ID_UNKNOWN; + + switch (id->xid.type) { + case ID_TYPE_UID: { + u_filter = talloc_asprintf_append_buffer( + u_filter, "(%s=%ju)", ctx->schema->uid, + (uintmax_t)id->xid.id); + if (u_filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + break; + } + + case ID_TYPE_GID: { + g_filter = talloc_asprintf_append_buffer( + g_filter, "(%s=%ju)", ctx->schema->gid, + (uintmax_t)id->xid.id); + if (g_filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + break; + } + + default: + DBG_WARNING("Unknown id type: %u\n", + (unsigned)id->xid.type); + break; + } + } + + filter = talloc_strdup(talloc_tos(), "(|"); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (*u_filter != '\0') { + filter = talloc_asprintf_append_buffer( + filter, + "(&(|(sAMAccountType=%d)(sAMAccountType=%d)" + "(sAMAccountType=%d))(|%s))", + ATYPE_NORMAL_ACCOUNT, ATYPE_WORKSTATION_TRUST, + ATYPE_INTERDOMAIN_TRUST, u_filter); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + TALLOC_FREE(u_filter); + + if (*g_filter != '\0') { + filter = talloc_asprintf_append_buffer( + filter, + "(&(|(sAMAccountType=%d)(sAMAccountType=%d))(|%s))", + ATYPE_SECURITY_GLOBAL_GROUP, + ATYPE_SECURITY_LOCAL_GROUP, + g_filter); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + TALLOC_FREE(g_filter); + + filter = talloc_asprintf_append_buffer(filter, ")"); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("Filter: [%s]\n", filter); + + rc = tldap_search(ctx->ld, ctx->default_nc, TLDAP_SCOPE_SUB, filter, + attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0, + 0, 0, 0, talloc_tos(), &msgs); + if (!TLDAP_RC_IS_SUCCESS(rc)) { + return NT_STATUS_LDAP(TLDAP_RC_V(rc)); + } + + TALLOC_FREE(filter); + + num_msgs = talloc_array_length(msgs); + + for (i=0; i<num_msgs; i++) { + struct tldap_message *msg = msgs[i]; + char *dn; + struct id_map *map; + struct dom_sid sid; + size_t j; + bool ok; + uint32_t atype, xid; + enum id_type type; + struct dom_sid_buf sidbuf; + + if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) { + continue; + } + + ok = tldap_entry_dn(msg, &dn); + if (!ok) { + DBG_DEBUG("No dn found in msg %zu\n", i); + continue; + } + + ok = idmap_ad_dn_filter(dom, dn); + if (!ok) { + DBG_DEBUG("%s filtered out\n", dn); + continue; + } + + ok = tldap_pull_uint32(msg, "sAMAccountType", &atype); + if (!ok) { + DBG_DEBUG("No atype in object %s\n", dn); + continue; + } + + switch (atype & 0xF0000000) { + case ATYPE_SECURITY_GLOBAL_GROUP: + case ATYPE_SECURITY_LOCAL_GROUP: + type = ID_TYPE_GID; + break; + case ATYPE_NORMAL_ACCOUNT: + case ATYPE_WORKSTATION_TRUST: + case ATYPE_INTERDOMAIN_TRUST: + type = ID_TYPE_UID; + break; + default: + DBG_WARNING("unrecognized SAM account type %08x\n", + atype); + continue; + } + + ok = tldap_pull_uint32(msg, (type == ID_TYPE_UID) ? + ctx->schema->uid : ctx->schema->gid, + &xid); + if (!ok) { + DBG_WARNING("No unix id in object %s\n", dn); + continue; + } + + ok = tldap_pull_binsid(msg, "objectSid", &sid); + if (!ok) { + DBG_DEBUG("No objectSid in object %s\n", dn); + continue; + } + + map = NULL; + for (j=0; ids[j]; j++) { + if ((type == ids[j]->xid.type) && + (xid == ids[j]->xid.id)) { + map = ids[j]; + break; + } + } + if (map == NULL) { + DBG_DEBUG("Got unexpected sid %s from object %s\n", + dom_sid_str_buf(&sid, &sidbuf), + dn); + continue; + } + + sid_copy(map->sid, &sid); + map->status = ID_MAPPED; + + DBG_DEBUG("Mapped %s -> %ju (%d)\n", + dom_sid_str_buf(map->sid, &sidbuf), + (uintmax_t)map->xid.id, map->xid.type); + } + + TALLOC_FREE(msgs); + + return NT_STATUS_OK; +} + +static NTSTATUS idmap_ad_sids_to_unixids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct idmap_ad_context *ctx; + TLDAPRC rc; + NTSTATUS status; + struct tldap_message **msgs; + + char *filter; + size_t i, num_msgs; + + const char *attrs[] = { + "sAMAccountType", + "objectSid", + NULL, /* attr_uidnumber */ + NULL, /* attr_gidnumber */ + }; + + status = idmap_ad_get_context(dom, &ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + attrs[2] = ctx->schema->uid; + attrs[3] = ctx->schema->gid; + + filter = talloc_asprintf( + talloc_tos(), + "(&(|(sAMAccountType=%d)(sAMAccountType=%d)(sAMAccountType=%d)" + "(sAMAccountType=%d)(sAMAccountType=%d))(|", + ATYPE_NORMAL_ACCOUNT, ATYPE_WORKSTATION_TRUST, + ATYPE_INTERDOMAIN_TRUST, ATYPE_SECURITY_GLOBAL_GROUP, + ATYPE_SECURITY_LOCAL_GROUP); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; ids[i]; i++) { + char *sidstr; + + ids[i]->status = ID_UNKNOWN; + + sidstr = ldap_encode_ndr_dom_sid(talloc_tos(), ids[i]->sid); + if (sidstr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf_append_buffer( + filter, "(objectSid=%s)", sidstr); + TALLOC_FREE(sidstr); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + filter = talloc_asprintf_append_buffer(filter, "))"); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DBG_DEBUG("Filter: [%s]\n", filter); + + rc = tldap_search(ctx->ld, ctx->default_nc, TLDAP_SCOPE_SUB, filter, + attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0, + 0, 0, 0, talloc_tos(), &msgs); + if (!TLDAP_RC_IS_SUCCESS(rc)) { + return NT_STATUS_LDAP(TLDAP_RC_V(rc)); + } + + TALLOC_FREE(filter); + + num_msgs = talloc_array_length(msgs); + + for (i=0; i<num_msgs; i++) { + struct tldap_message *msg = msgs[i]; + char *dn; + struct id_map *map; + struct dom_sid sid; + size_t j; + bool ok; + uint64_t account_type, xid; + enum id_type type; + struct dom_sid_buf buf; + + if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) { + continue; + } + + ok = tldap_entry_dn(msg, &dn); + if (!ok) { + DBG_DEBUG("No dn found in msg %zu\n", i); + continue; + } + + ok = idmap_ad_dn_filter(dom, dn); + if (!ok) { + DBG_DEBUG("%s filtered out\n", dn); + continue; + } + + ok = tldap_pull_binsid(msg, "objectSid", &sid); + if (!ok) { + DBG_DEBUG("No objectSid in object %s\n", dn); + continue; + } + + map = NULL; + for (j=0; ids[j]; j++) { + if (dom_sid_equal(&sid, ids[j]->sid)) { + map = ids[j]; + break; + } + } + if (map == NULL) { + DBG_DEBUG("Got unexpected sid %s from object %s\n", + dom_sid_str_buf(&sid, &buf), + dn); + continue; + } + + ok = tldap_pull_uint64(msg, "sAMAccountType", &account_type); + if (!ok) { + DBG_DEBUG("No sAMAccountType in %s\n", dn); + continue; + } + + switch (account_type & 0xF0000000) { + case ATYPE_SECURITY_GLOBAL_GROUP: + case ATYPE_SECURITY_LOCAL_GROUP: + type = ID_TYPE_GID; + break; + case ATYPE_NORMAL_ACCOUNT: + case ATYPE_WORKSTATION_TRUST: + case ATYPE_INTERDOMAIN_TRUST: + type = ID_TYPE_UID; + break; + default: + DBG_WARNING("unrecognized SAM account type %"PRIu64"\n", + account_type); + continue; + } + + ok = tldap_pull_uint64(msg, + type == ID_TYPE_UID ? + ctx->schema->uid : ctx->schema->gid, + &xid); + if (!ok) { + DBG_DEBUG("No xid in %s\n", dn); + continue; + } + + /* mapped */ + map->xid.type = type; + map->xid.id = xid; + map->status = ID_MAPPED; + + DEBUG(10, ("Mapped %s -> %lu (%d)\n", + dom_sid_str_buf(map->sid, &buf), + (unsigned long)map->xid.id, map->xid.type)); + } + + TALLOC_FREE(msgs); + + return NT_STATUS_OK; +} + +static NTSTATUS idmap_ad_unixids_to_sids_retry(struct idmap_domain *dom, + struct id_map **ids) +{ + const NTSTATUS status_server_down = + NT_STATUS_LDAP(TLDAP_RC_V(TLDAP_SERVER_DOWN)); + NTSTATUS status; + + status = idmap_ad_unixids_to_sids(dom, ids); + + if (NT_STATUS_EQUAL(status, status_server_down)) { + TALLOC_FREE(dom->private_data); + status = idmap_ad_unixids_to_sids(dom, ids); + } + + return status; +} + +static NTSTATUS idmap_ad_sids_to_unixids_retry(struct idmap_domain *dom, + struct id_map **ids) +{ + const NTSTATUS status_server_down = + NT_STATUS_LDAP(TLDAP_RC_V(TLDAP_SERVER_DOWN)); + NTSTATUS status; + + status = idmap_ad_sids_to_unixids(dom, ids); + + if (NT_STATUS_EQUAL(status, status_server_down)) { + TALLOC_FREE(dom->private_data); + status = idmap_ad_sids_to_unixids(dom, ids); + } + + return status; +} + +static const struct idmap_methods ad_methods = { + .init = idmap_ad_initialize, + .unixids_to_sids = idmap_ad_unixids_to_sids_retry, + .sids_to_unixids = idmap_ad_sids_to_unixids_retry, +}; + +static_decl_idmap; +NTSTATUS idmap_ad_init(TALLOC_CTX *ctx) +{ + NTSTATUS status; + + status = smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, + "ad", &ad_methods); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = idmap_ad_nss_init(ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/idmap_ad_nss.c b/source3/winbindd/idmap_ad_nss.c new file mode 100644 index 0000000..3120280 --- /dev/null +++ b/source3/winbindd/idmap_ad_nss.c @@ -0,0 +1,418 @@ +/* + * idmap_ad: map between Active Directory and RFC 2307 or "Services for Unix" (SFU) Accounts + * + * Unix SMB/CIFS implementation. + * + * Winbind ADS backend functions + * + * Copyright (C) Andrew Tridgell 2001 + * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003 + * Copyright (C) Gerald (Jerry) Carter 2004-2007 + * Copyright (C) Luke Howard 2001-2004 + * Copyright (C) Michael Adam 2008,2010 + * + * 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.h" +#include "../libds/common/flags.h" +#include "winbindd_ads.h" +#include "libads/ldap_schema.h" +#include "nss_info.h" +#include "idmap.h" +#include "../libcli/ldap/ldap_ndr.h" +#include "../libcli/security/security.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +#define CHECK_ALLOC_DONE(mem) do { \ + if (!mem) { \ + DEBUG(0, ("Out of memory!\n")); \ + ret = NT_STATUS_NO_MEMORY; \ + goto done; \ + } \ +} while (0) + +struct idmap_ad_context { + ADS_STRUCT *ads; + struct posix_schema *ad_schema; + enum wb_posix_mapping ad_map_type; /* WB_POSIX_MAP_UNKNOWN */ +}; + +/************************************************************************ + ***********************************************************************/ + +static ADS_STATUS ad_idmap_cached_connection(struct idmap_domain *dom) +{ + ADS_STATUS status; + struct idmap_ad_context * ctx; + + DEBUG(10, ("ad_idmap_cached_connection: called for domain '%s'\n", + dom->name)); + + ctx = talloc_get_type(dom->private_data, struct idmap_ad_context); + + status = ads_idmap_cached_connection(dom->name, ctx, &ctx->ads); + if (!ADS_ERR_OK(status)) { + return status; + } + + /* if we have a valid ADS_STRUCT and the schema model is + defined, then we can return here. */ + + if ( ctx->ad_schema ) { + return ADS_SUCCESS; + } + + /* Otherwise, set the schema model */ + + if ( (ctx->ad_map_type == WB_POSIX_MAP_SFU) || + (ctx->ad_map_type == WB_POSIX_MAP_SFU20) || + (ctx->ad_map_type == WB_POSIX_MAP_RFC2307) ) + { + status = ads_check_posix_schema_mapping( + ctx, ctx->ads, ctx->ad_map_type, &ctx->ad_schema); + if ( !ADS_ERR_OK(status) ) { + DEBUG(2,("ad_idmap_cached_connection: Failed to obtain schema details!\n")); + } + } + + return status; +} + +/* + * nss_info_{sfu,sfu20,rfc2307} + */ + +/************************************************************************ + Initialize the {sfu,sfu20,rfc2307} state + ***********************************************************************/ + +static const char *wb_posix_map_unknown_string = "WB_POSIX_MAP_UNKNOWN"; +static const char *wb_posix_map_template_string = "WB_POSIX_MAP_TEMPLATE"; +static const char *wb_posix_map_sfu_string = "WB_POSIX_MAP_SFU"; +static const char *wb_posix_map_sfu20_string = "WB_POSIX_MAP_SFU20"; +static const char *wb_posix_map_rfc2307_string = "WB_POSIX_MAP_RFC2307"; +static const char *wb_posix_map_unixinfo_string = "WB_POSIX_MAP_UNIXINFO"; + +static const char *ad_map_type_string(enum wb_posix_mapping map_type) +{ + switch (map_type) { + case WB_POSIX_MAP_TEMPLATE: + return wb_posix_map_template_string; + case WB_POSIX_MAP_SFU: + return wb_posix_map_sfu_string; + case WB_POSIX_MAP_SFU20: + return wb_posix_map_sfu20_string; + case WB_POSIX_MAP_RFC2307: + return wb_posix_map_rfc2307_string; + case WB_POSIX_MAP_UNIXINFO: + return wb_posix_map_unixinfo_string; + default: + return wb_posix_map_unknown_string; + } +} + +static NTSTATUS nss_ad_generic_init(struct nss_domain_entry *e, + enum wb_posix_mapping new_ad_map_type) +{ + struct idmap_domain *dom; + struct idmap_ad_context *ctx; + + if (e->state != NULL) { + dom = talloc_get_type(e->state, struct idmap_domain); + } else { + dom = talloc_zero(e, struct idmap_domain); + if (dom == NULL) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + e->state = dom; + } + + if (e->domain != NULL) { + dom->name = talloc_strdup(dom, e->domain); + if (dom->name == NULL) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + } + + if (dom->private_data != NULL) { + ctx = talloc_get_type(dom->private_data, + struct idmap_ad_context); + } else { + ctx = talloc_zero(dom, struct idmap_ad_context); + if (ctx == NULL) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + ctx->ad_map_type = WB_POSIX_MAP_RFC2307; + dom->private_data = ctx; + } + + if ((ctx->ad_map_type != WB_POSIX_MAP_UNKNOWN) && + (ctx->ad_map_type != new_ad_map_type)) + { + DEBUG(2, ("nss_ad_generic_init: " + "Warning: overriding previously set posix map type " + "%s for domain %s with map type %s.\n", + ad_map_type_string(ctx->ad_map_type), + dom->name, + ad_map_type_string(new_ad_map_type))); + } + + ctx->ad_map_type = new_ad_map_type; + + return NT_STATUS_OK; +} + +static NTSTATUS nss_sfu_init( struct nss_domain_entry *e ) +{ + return nss_ad_generic_init(e, WB_POSIX_MAP_SFU); +} + +static NTSTATUS nss_sfu20_init( struct nss_domain_entry *e ) +{ + return nss_ad_generic_init(e, WB_POSIX_MAP_SFU20); +} + +static NTSTATUS nss_rfc2307_init( struct nss_domain_entry *e ) +{ + return nss_ad_generic_init(e, WB_POSIX_MAP_RFC2307); +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_ad_map_to_alias(TALLOC_CTX *mem_ctx, + struct nss_domain_entry *e, + const char *name, + char **alias) +{ + 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; + struct idmap_domain *dom; + struct idmap_ad_context *ctx = NULL; + + /* Check incoming parameters */ + + if ( !e || !e->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; + } + + dom = talloc_get_type(e->state, struct idmap_domain); + ctx = talloc_get_type(dom->private_data, struct idmap_ad_context); + + ads_status = ad_idmap_cached_connection(dom); + if (!ADS_ERR_OK(ads_status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (!ctx->ad_schema) { + nt_status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + goto done; + } + + attrs[0] = ctx->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(ctx->ads, &msg, filter, attrs); + if (!ADS_ERR_OK(ads_status)) { + nt_status = ads_ntstatus(ads_status); + goto done; + } + + *alias = ads_pull_string(ctx->ads, mem_ctx, msg, ctx->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(ctx->ads, msg); + } + + return nt_status; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_ad_map_from_alias( TALLOC_CTX *mem_ctx, + struct nss_domain_entry *e, + const char *alias, + char **name ) +{ + 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 = NULL; + struct idmap_domain *dom; + struct idmap_ad_context *ctx = NULL; + + /* 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; + } + + dom = talloc_get_type(e->state, struct idmap_domain); + ctx = talloc_get_type(dom->private_data, struct idmap_ad_context); + + ads_status = ad_idmap_cached_connection(dom); + if (!ADS_ERR_OK(ads_status)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (!ctx->ad_schema) { + nt_status = NT_STATUS_OBJECT_PATH_NOT_FOUND; + goto done; + } + + filter = talloc_asprintf(mem_ctx, + "(%s=%s)", + ctx->ad_schema->posix_uid_attr, + alias); + if (!filter) { + nt_status = NT_STATUS_NO_MEMORY; + goto done; + } + + ads_status = ads_search_retry(ctx->ads, &msg, filter, attrs); + if (!ADS_ERR_OK(ads_status)) { + nt_status = ads_ntstatus(ads_status); + goto done; + } + + username = ads_pull_string(ctx->ads, mem_ctx, msg, + "sAMAccountName"); + if (!username) { + nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto done; + } + + *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: + TALLOC_FREE(username); + TALLOC_FREE(filter); + if (msg) { + ads_msgfree(ctx->ads, msg); + } + + return nt_status; +} + +/************************************************************************ + Function dispatch tables for the idmap and nss plugins + ***********************************************************************/ + +/* The SFU and RFC2307 NSS plugins share everything but the init + function which sets the intended schema model to use */ + +static const struct nss_info_methods nss_rfc2307_methods = { + .init = nss_rfc2307_init, + .map_to_alias = nss_ad_map_to_alias, + .map_from_alias = nss_ad_map_from_alias, +}; + +static const struct nss_info_methods nss_sfu_methods = { + .init = nss_sfu_init, + .map_to_alias = nss_ad_map_to_alias, + .map_from_alias = nss_ad_map_from_alias, +}; + +static const struct nss_info_methods nss_sfu20_methods = { + .init = nss_sfu20_init, + .map_to_alias = nss_ad_map_to_alias, + .map_from_alias = nss_ad_map_from_alias, +}; + + + +/************************************************************************ + Initialize the plugins + ***********************************************************************/ + +NTSTATUS idmap_ad_nss_init(TALLOC_CTX *mem_ctx) +{ + NTSTATUS status; + + status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION, + "rfc2307", &nss_rfc2307_methods); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION, + "sfu", &nss_sfu_methods); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION, + "sfu20", &nss_sfu20_methods); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/idmap_autorid.c b/source3/winbindd/idmap_autorid.c new file mode 100644 index 0000000..bf5947a --- /dev/null +++ b/source3/winbindd/idmap_autorid.c @@ -0,0 +1,945 @@ +/* + * idmap_autorid: static map between Active Directory/NT RIDs + * and RFC 2307 accounts + * + * based on the idmap_rid module, but this module defines the ranges + * for the domains by automatically allocating a range for each domain + * + * Copyright (C) Christian Ambach, 2010-2012 + * + * 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/>. + * + */ + +/* + * This module allocates ranges for domains to be used in a + * algorithmic mode like idmap_rid. Multiple ranges are supported + * for a single domain: If a rid exceeds the range size, a matching + * range is allocated to hold the rid's id. + * + * Here are the formulas applied: + * + * + * For a sid of the form domain_sid-rid, we have + * + * rid = reduced_rid + domain_range_index * range_size + * + * with + * reduced_rid := rid % range_size + * domain_range_index := rid / range_size + * + * And reduced_rid fits into a range. + * + * In the database, we associate a range_number to + * the pair domain_sid,domain_range_index. + * + * Now the unix id for the given sid calculates as: + * + * id = reduced_rid + range_low_id + * + * with + * + * range_low_id = low_id + range_number * range_size + * + * + * The inverse calculation goes like this: + * + * Given a unix id, let + * + * normalized_id := id - low_id + * reduced_rid := normalized_id % range_size + * range_number = normalized_id / range_size + * + * Then we have + * + * id = reduced_rid + low_id + range_number * range_size + * + * From the database, get the domain_sid,domain_range_index pair + * belonging to the range_number (if there is already one). + * + * Then the rid for the unix id calculates as: + * + * rid = reduced_rid + domain_range_index * range_size + */ + +#include "idmap_autorid_tdb.h" +#include "winbindd.h" +#include "idmap.h" +#include "idmap_rw.h" +#include "../libcli/security/dom_sid.h" +#include "libsmb/samlogon_cache.h" +#include "passdb/machine_sid.h" +#include "lib/util/string_wrappers.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +#define IDMAP_AUTORID_ALLOC_RESERVED 500 + +/* handle to the tdb storing domain <-> range assignments */ +static struct db_context *autorid_db; + +static bool ignore_builtin = false; + +static NTSTATUS idmap_autorid_get_alloc_range(struct idmap_domain *dom, + struct autorid_range_config *range) +{ + NTSTATUS status; + + ZERO_STRUCT(*range); + + fstrcpy(range->domsid, ALLOC_RANGE); + + status = idmap_autorid_get_domainrange(autorid_db, + range, + dom->read_only); + + return status; +} + +static NTSTATUS idmap_autorid_allocate_id(struct idmap_domain *dom, + struct unixid *xid) { + + NTSTATUS ret; + struct autorid_range_config range; + + if (dom->read_only) { + DEBUG(3, ("Backend is read-only, refusing " + "new allocation request\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + /* fetch the range for the allocation pool */ + + ret = idmap_autorid_get_alloc_range(dom, &range); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(3, ("Could not determine range for allocation pool, " + "check previous messages for reason\n")); + return ret; + } + + ret = idmap_tdb_common_get_new_id(dom, xid); + + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Fatal error while allocating new ID!\n")); + return ret; + } + + xid->id = xid->id + range.low_id; + + DEBUG(10, ("Returned new %s %d from allocation range\n", + (xid->type==ID_TYPE_UID)?"uid":"gid", xid->id)); + + return ret; +} + +/* + * map a xid to SID using the idmap_tdb like pool + */ +static NTSTATUS idmap_autorid_id_to_sid_alloc(struct idmap_domain *dom, + struct id_map *map) +{ + NTSTATUS ret; + + /* look out for the mapping */ + ret = idmap_tdb_common_unixid_to_sid(dom, map); + + if (NT_STATUS_IS_OK(ret)) { + map->status = ID_MAPPED; + return ret; + } + + map->status = ID_UNKNOWN; + + DEBUG(10, ("no ID->SID mapping for %d could be found\n", map->xid.id)); + + return ret; +} + +static NTSTATUS idmap_autorid_id_to_sid(struct autorid_global_config *cfg, + struct idmap_domain *dom, + struct id_map *map) +{ + uint32_t range_number; + uint32_t domain_range_index; + uint32_t normalized_id; + uint32_t reduced_rid; + uint32_t rid; + TDB_DATA data = tdb_null; + char *keystr; + struct dom_sid domsid; + NTSTATUS status; + bool ok; + const char *q = NULL; + + /* can this be one of our ids? */ + if (map->xid.id < cfg->minvalue) { + DEBUG(10, ("id %d is lower than minimum value, " + "ignoring mapping request\n", map->xid.id)); + map->status = ID_UNKNOWN; + return NT_STATUS_OK; + } + + if (map->xid.id > (cfg->minvalue + cfg->rangesize * cfg->maxranges)) { + DEBUG(10, ("id %d is outside of maximum id value, " + "ignoring mapping request\n", map->xid.id)); + map->status = ID_UNKNOWN; + return NT_STATUS_OK; + } + + /* determine the range of this uid */ + + normalized_id = map->xid.id - cfg->minvalue; + range_number = normalized_id / cfg->rangesize; + + keystr = talloc_asprintf(talloc_tos(), "%u", range_number); + if (!keystr) { + return NT_STATUS_NO_MEMORY; + } + + status = dbwrap_fetch_bystring(autorid_db, talloc_tos(), keystr, &data); + TALLOC_FREE(keystr); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(4, ("id %d belongs to range %d which does not have " + "domain mapping, ignoring mapping request\n", + map->xid.id, range_number)); + TALLOC_FREE(data.dptr); + map->status = ID_UNKNOWN; + return NT_STATUS_OK; + } + + if ((data.dsize == 0) || (data.dptr[data.dsize-1] != '\0')) { + DBG_WARNING("Invalid range %"PRIu32"\n", range_number); + TALLOC_FREE(data.dptr); + map->status = ID_UNKNOWN; + return NT_STATUS_OK; + } + + if (strncmp((const char *)data.dptr, + ALLOC_RANGE, + strlen(ALLOC_RANGE)) == 0) { + /* + * this is from the alloc range, check if there is a mapping + */ + DEBUG(5, ("id %d belongs to allocation range, " + "checking for mapping\n", + map->xid.id)); + TALLOC_FREE(data.dptr); + return idmap_autorid_id_to_sid_alloc(dom, map); + } + + ok = dom_sid_parse_endp((const char *)data.dptr, &domsid, &q); + if (!ok) { + TALLOC_FREE(data.dptr); + map->status = ID_UNKNOWN; + return NT_STATUS_OK; + } + + /* + * Allow for sid#range_index, just sid is range index 0 + */ + + switch (*q) { + case '\0': + domain_range_index = 0; + break; + case '#': + if (sscanf(q+1, "%"SCNu32, &domain_range_index) == 1) { + break; + } + /* If we end up here, something weird is in the record. */ + + FALL_THROUGH; + default: + DBG_DEBUG("SID/domain range: %s\n", + (const char *)data.dptr); + TALLOC_FREE(data.dptr); + map->status = ID_UNKNOWN; + return NT_STATUS_OK; + } + + TALLOC_FREE(data.dptr); + + reduced_rid = normalized_id % cfg->rangesize; + rid = reduced_rid + domain_range_index * cfg->rangesize; + + sid_compose(map->sid, &domsid, rid); + + /* We **really** should have some way of validating + the SID exists and is the correct type here. But + that is a deficiency in the idmap_rid design. */ + + map->status = ID_MAPPED; + map->xid.type = ID_TYPE_BOTH; + + return NT_STATUS_OK; +} + +/********************************** + Single sid to id lookup function. +**********************************/ + +static NTSTATUS idmap_autorid_sid_to_id_rid( + uint32_t rangesize, + uint32_t low_id, + struct id_map *map) +{ + uint32_t rid; + uint32_t reduced_rid; + + sid_peek_rid(map->sid, &rid); + + reduced_rid = rid % rangesize; + + map->xid.id = reduced_rid + low_id; + map->xid.type = ID_TYPE_BOTH; + map->status = ID_MAPPED; + + return NT_STATUS_OK; +} + +/********************************** + lookup a set of unix ids. +**********************************/ + +static NTSTATUS idmap_autorid_unixids_to_sids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct idmap_tdb_common_context *commoncfg; + struct autorid_global_config *globalcfg; + NTSTATUS ret; + int i; + int num_tomap = 0; + int num_mapped = 0; + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + num_tomap++; + } + + commoncfg = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + globalcfg = talloc_get_type(commoncfg->private_data, + struct autorid_global_config); + + for (i = 0; ids[i]; i++) { + + ret = idmap_autorid_id_to_sid(globalcfg, dom, ids[i]); + + if ((!NT_STATUS_IS_OK(ret)) && + (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) { + /* some fatal error occurred, log it */ + DBG_NOTICE("Unexpected error resolving an ID " + "(%d): %s\n", ids[i]->xid.id, + nt_errstr(ret)); + goto failure; + } + + if (NT_STATUS_IS_OK(ret) && ids[i]->status == ID_MAPPED) { + num_mapped++; + } + + } + + if (num_tomap == num_mapped) { + return NT_STATUS_OK; + } + if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + + return STATUS_SOME_UNMAPPED; + + + failure: + return ret; +} + +static bool idmap_autorid_sid_is_special(struct dom_sid *sid) +{ + bool match; + + match = sid_check_is_in_wellknown_domain(sid); + if (match) { + return true; + } + + return false; +} + +static NTSTATUS idmap_autorid_sid_to_id_special(struct idmap_domain *dom, + struct id_map *map) +{ + struct idmap_tdb_common_context *common = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + uint32_t count; + struct autorid_range_config range; + NTSTATUS status; + uint32_t free_id; + + status = idmap_autorid_get_alloc_range(dom, &range); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Take the next free ID, counting from the top */ + free_id = 0; + for (count = 0; count < IDMAP_AUTORID_ALLOC_RESERVED; count++) { + struct id_map test_map; + struct dom_sid sid; + + test_map.sid = &sid; + test_map.xid.type = map->xid.type; + test_map.xid.id = range.high_id - count; + test_map.status = ID_UNKNOWN; + + status = idmap_tdb_common_unixid_to_sid(dom, &test_map); + if (NT_STATUS_EQUAL(NT_STATUS_NONE_MAPPED, status)) { + free_id = test_map.xid.id; + break; + } + + if (!NT_STATUS_IS_OK(status)) { + /* error - get out */ + return status; + } + + /* mapping exists - try next ID */ + } + + if (free_id == 0) { + return NT_STATUS_NONE_MAPPED; + } + + map->status = ID_MAPPED; + map->xid.id = free_id; + + status = common->rw_ops->set_mapping(dom, map); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("Error storing new mapping: %s\n", + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +struct idmap_autorid_sid_to_id_alloc_ctx { + struct idmap_domain *dom; + struct id_map *map; +}; + +static NTSTATUS idmap_autorid_sid_to_id_alloc_action( + struct db_context *db, + void *private_data) +{ + struct idmap_autorid_sid_to_id_alloc_ctx *ctx; + + ctx = (struct idmap_autorid_sid_to_id_alloc_ctx *)private_data; + + if (idmap_autorid_sid_is_special(ctx->map->sid)) { + struct dom_sid_buf buf; + NTSTATUS ret; + + ret = idmap_autorid_sid_to_id_special(ctx->dom, ctx->map); + if (NT_STATUS_IS_OK(ret)) { + return NT_STATUS_OK; + } + if (!NT_STATUS_EQUAL(NT_STATUS_NONE_MAPPED, ret)) { + return ret; + } + + DEBUG(10, ("Special sid %s not mapped. falling back to " + "regular allocation\n", + dom_sid_str_buf(ctx->map->sid, &buf))); + } + + return idmap_tdb_common_new_mapping(ctx->dom, ctx->map); +} + +/* + * map a SID to xid using the idmap_tdb like pool + */ +static NTSTATUS idmap_autorid_sid_to_id_alloc( + struct idmap_tdb_common_context *ctx, + struct idmap_domain *dom, + struct id_map *map) +{ + NTSTATUS ret; + struct idmap_autorid_sid_to_id_alloc_ctx alloc_ctx; + struct dom_sid_buf buf; + + map->status = ID_UNKNOWN; + + /* see if we already have a mapping */ + ret = idmap_tdb_common_sid_to_unixid(dom, map); + + if (NT_STATUS_IS_OK(ret)) { + map->status = ID_MAPPED; + return ret; + } + + /* bad things happened */ + if (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) { + DEBUG(1, ("Looking up SID->ID mapping for %s failed: %s\n", + dom_sid_str_buf(map->sid, &buf), + nt_errstr(ret))); + return ret; + } + + if (dom->read_only) { + DEBUG(3, ("Not allocating new mapping for %s, because backend " + "is read-only\n", + dom_sid_str_buf(map->sid, &buf))); + map->status = ID_UNMAPPED; + return NT_STATUS_NONE_MAPPED; + } + + DEBUG(10, ("Creating new mapping in pool for %s\n", + dom_sid_str_buf(map->sid, &buf))); + + alloc_ctx.dom = dom; + alloc_ctx.map = map; + + ret = dbwrap_trans_do(ctx->db, idmap_autorid_sid_to_id_alloc_action, + &alloc_ctx); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Failed to create a new mapping in alloc range: %s\n", + nt_errstr(ret))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + map->status = ID_MAPPED; + return NT_STATUS_OK; +} + +static bool idmap_autorid_domsid_is_for_alloc(struct dom_sid *sid) +{ + bool match; + + match = sid_check_is_wellknown_domain(sid, NULL); + if (match) { + return true; + } + + return false; +} + +static NTSTATUS idmap_autorid_sid_to_id(struct idmap_tdb_common_context *common, + struct idmap_domain *dom, + struct id_map *map) +{ + struct autorid_global_config *global = + talloc_get_type_abort(common->private_data, + struct autorid_global_config); + struct autorid_range_config range; + uint32_t rid; + struct dom_sid domainsid; + struct dom_sid_buf buf; + NTSTATUS ret; + + ZERO_STRUCT(range); + map->status = ID_UNKNOWN; + + DEBUG(10, ("Trying to map %s\n", dom_sid_str_buf(map->sid, &buf))); + + sid_copy(&domainsid, map->sid); + if (!sid_split_rid(&domainsid, &rid)) { + DEBUG(4, ("Could not determine domain SID from %s, " + "ignoring mapping request\n", + dom_sid_str_buf(map->sid, &buf))); + map->status = ID_UNMAPPED; + return NT_STATUS_NONE_MAPPED; + } + + if (idmap_autorid_domsid_is_for_alloc(&domainsid)) { + DEBUG(10, ("SID %s is for ALLOC range.\n", + dom_sid_str_buf(map->sid, &buf))); + + return idmap_autorid_sid_to_id_alloc(common, dom, map); + } + + if (dom_sid_equal(&domainsid, &global_sid_Builtin) && ignore_builtin) { + DEBUG(10, ("Ignoring request for BUILTIN domain\n")); + map->status = ID_UNMAPPED; + return NT_STATUS_NONE_MAPPED; + } + + sid_to_fstring(range.domsid, &domainsid); + + range.domain_range_index = rid / (global->rangesize); + + ret = idmap_autorid_getrange(autorid_db, range.domsid, + range.domain_range_index, + &range.rangenum, &range.low_id); + if (NT_STATUS_IS_OK(ret)) { + return idmap_autorid_sid_to_id_rid( + global->rangesize, range.low_id, map); + } + + if (dom->read_only) { + DBG_DEBUG("read-only is enabled, did not allocate " + "new range for domain %s\n", range.domsid); + map->status = ID_UNMAPPED; + return NT_STATUS_NONE_MAPPED; + } + + /* + * Check if we should allocate a domain range. We need to + * protect against unknown domains to not fill our ranges + * needlessly. + */ + + if (sid_check_is_builtin(&domainsid) || + sid_check_is_our_sam(&domainsid)) { + goto allocate; + } + + { + struct winbindd_domain *domain; + + /* + * Deterministic check for domain members: We can be + * sure that the domain we are member of is worth to + * add a mapping for. + */ + + domain = find_our_domain(); + if ((domain != NULL) && + dom_sid_equal(&domain->sid, &domainsid)) { + goto allocate; + } + } + + /* + * If we have already allocated range index 0, this domain is + * worth allocating for in higher ranges. + */ + if (range.domain_range_index != 0) { + uint32_t zero_rangenum, zero_low_id; + + ret = idmap_autorid_getrange(autorid_db, range.domsid, 0, + &zero_rangenum, &zero_low_id); + if (NT_STATUS_IS_OK(ret)) { + goto allocate; + } + } + + /* + * If the caller already did a lookup sid and made sure the + * domain sid is valid, we can allocate a new range. + * + * Currently the winbindd parent already does a lookup sids + * first, but hopefully changes in future. If the + * caller knows the domain sid, ID_TYPE_BOTH should be + * passed instead of ID_TYPE_NOT_SPECIFIED. + */ + if (map->xid.type != ID_TYPE_NOT_SPECIFIED) { + goto allocate; + } + + /* + * Check of last resort: A domain is valid if a user from that + * domain has recently logged in. The samlogon_cache these + * days also stores the domain sid. + * + * We used to check the list of trusted domains we received + * from "our" dc, but this is not reliable enough. + */ + if (netsamlogon_cache_have(&domainsid)) { + goto allocate; + } + + /* + * Nobody knows this domain, so refuse to allocate a fresh + * range. + */ + + DBG_NOTICE("Allocating range for domain %s required type_hint\n", range.domsid); + map->status = ID_REQUIRE_TYPE; + return NT_STATUS_SOME_NOT_MAPPED; + +allocate: + ret = idmap_autorid_acquire_range(autorid_db, &range); + if (!NT_STATUS_IS_OK(ret)) { + DBG_NOTICE("Could not determine range for domain: %s, " + "check previous messages for reason\n", + nt_errstr(ret)); + return ret; + } + + return idmap_autorid_sid_to_id_rid(global->rangesize, range.low_id, + map); +} + +/********************************** + lookup a set of sids. +**********************************/ + +static NTSTATUS idmap_autorid_sids_to_unixids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct idmap_tdb_common_context *commoncfg; + NTSTATUS ret; + size_t i; + size_t num_tomap = 0; + size_t num_mapped = 0; + size_t num_required = 0; + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + num_tomap++; + } + + commoncfg = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + for (i = 0; ids[i]; i++) { + ret = idmap_autorid_sid_to_id(commoncfg, dom, ids[i]); + if (NT_STATUS_EQUAL(ret, NT_STATUS_SOME_NOT_MAPPED) && + ids[i]->status == ID_REQUIRE_TYPE) + { + num_required++; + continue; + } + if ((!NT_STATUS_IS_OK(ret)) && + (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) { + struct dom_sid_buf buf; + /* some fatal error occurred, log it */ + DEBUG(3, ("Unexpected error resolving a SID (%s)\n", + dom_sid_str_buf(ids[i]->sid, &buf))); + return ret; + } + + if (NT_STATUS_IS_OK(ret) && ids[i]->status == ID_MAPPED) { + num_mapped++; + } + } + + if (num_tomap == num_mapped) { + return NT_STATUS_OK; + } else if (num_required > 0) { + return STATUS_SOME_UNMAPPED; + } else if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + + return STATUS_SOME_UNMAPPED; +} + +static NTSTATUS idmap_autorid_preallocate_wellknown(struct idmap_domain *dom) +{ + const char *groups[] = { "S-1-1-0", "S-1-2-0", "S-1-2-1", + "S-1-3-0", "S-1-3-1", "S-1-3-2", "S-1-3-3", "S-1-3-4", + "S-1-5-1", "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6", + "S-1-5-7", "S-1-5-8", "S-1-5-9", "S-1-5-10", "S-1-5-11", + "S-1-5-12", "S-1-5-13", "S-1-5-14", "S-1-5-15", + "S-1-5-17", "S-1-5-18", "S-1-5-19", "S-1-5-20" + }; + + struct id_map **maps; + int i, num; + NTSTATUS status; + + if (dom->read_only) { + return NT_STATUS_OK; + } + + num = ARRAY_SIZE(groups); + + maps = talloc_array(talloc_tos(), struct id_map*, num+1); + if (!maps) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num; i++) { + maps[i] = talloc(maps, struct id_map); + if (maps[i] == NULL) { + talloc_free(maps); + return NT_STATUS_NO_MEMORY; + } + maps[i]->xid.type = ID_TYPE_GID; + maps[i]->sid = dom_sid_parse_talloc(maps, groups[i]); + } + + maps[num] = NULL; + + status = idmap_autorid_sids_to_unixids(dom, maps); + + DEBUG(10,("Preallocation run finished with status %s\n", + nt_errstr(status))); + + talloc_free(maps); + + return NT_STATUS_IS_OK(status)?NT_STATUS_OK:NT_STATUS_UNSUCCESSFUL; +} + +static NTSTATUS idmap_autorid_initialize_action(struct db_context *db, + void *private_data) +{ + struct idmap_domain *dom; + struct idmap_tdb_common_context *common; + struct autorid_global_config *config; + NTSTATUS status; + + dom = (struct idmap_domain *)private_data; + common = (struct idmap_tdb_common_context *)dom->private_data; + config = (struct autorid_global_config *)common->private_data; + + status = idmap_autorid_init_hwms(db); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = idmap_autorid_saveconfig(db, config); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to store configuration data!\n")); + return status; + } + + status = idmap_autorid_preallocate_wellknown(dom); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to preallocate wellknown sids: %s\n", + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS idmap_autorid_initialize(struct idmap_domain *dom) +{ + struct idmap_tdb_common_context *commonconfig; + struct autorid_global_config *config; + NTSTATUS status; + char *db_path; + + if (!strequal(dom->name, "*")) { + DEBUG(0, ("idmap_autorid_initialize: Error: autorid configured " + "for domain '%s'. But autorid can only be used for " + "the default idmap configuration.\n", dom->name)); + return NT_STATUS_INVALID_PARAMETER; + } + + commonconfig = talloc_zero(dom, struct idmap_tdb_common_context); + if (!commonconfig) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + dom->private_data = commonconfig; + + commonconfig->rw_ops = talloc_zero(commonconfig, struct idmap_rw_ops); + if (commonconfig->rw_ops == NULL) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + config = talloc_zero(commonconfig, struct autorid_global_config); + if (!config) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + commonconfig->private_data = config; + + config->minvalue = dom->low_id; + config->rangesize = idmap_config_int("*", "rangesize", 100000); + + config->maxranges = (dom->high_id - dom->low_id + 1) / + config->rangesize; + + if (config->maxranges < 2) { + DBG_WARNING("Allowed idmap range is not a least double the " + "size of the rangesize. Please increase idmap " + "range.\n"); + status = NT_STATUS_INVALID_PARAMETER; + goto error; + } + + /* check if the high-low limit is a multiple of the rangesize */ + if ((dom->high_id - dom->low_id + 1) % config->rangesize != 0) { + DEBUG(5, ("High uid-low uid difference of %d " + "is not a multiple of the rangesize %d, " + "limiting ranges to lower boundary number of %d\n", + (dom->high_id - dom->low_id + 1), config->rangesize, + config->maxranges)); + } + + DEBUG(5, ("%d domain ranges with a size of %d are available\n", + config->maxranges, config->rangesize)); + + ignore_builtin = idmap_config_bool("*", "ignore builtin", false); + + /* fill the TDB common configuration */ + + commonconfig->max_id = config->rangesize - 1 + - IDMAP_AUTORID_ALLOC_RESERVED; + commonconfig->hwmkey_uid = ALLOC_HWM_UID; + commonconfig->hwmkey_gid = ALLOC_HWM_GID; + commonconfig->rw_ops->get_new_id = idmap_autorid_allocate_id; + commonconfig->rw_ops->set_mapping = idmap_tdb_common_set_mapping; + + db_path = state_path(talloc_tos(), "autorid.tdb"); + if (db_path == NULL) { + status = NT_STATUS_NO_MEMORY; + goto error; + } + + status = idmap_autorid_db_open(db_path, + NULL, /* TALLOC_CTX */ + &autorid_db); + TALLOC_FREE(db_path); + if (!NT_STATUS_IS_OK(status)) { + goto error; + } + + commonconfig->db = autorid_db; + + status = dbwrap_trans_do(autorid_db, + idmap_autorid_initialize_action, + dom); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to init the idmap database: %s\n", + nt_errstr(status))); + goto error; + } + + goto done; + +error: + talloc_free(config); + +done: + return status; +} + +static const struct idmap_methods autorid_methods = { + .init = idmap_autorid_initialize, + .unixids_to_sids = idmap_autorid_unixids_to_sids, + .sids_to_unixids = idmap_autorid_sids_to_unixids, + .allocate_id = idmap_autorid_allocate_id +}; + +static_decl_idmap; +NTSTATUS idmap_autorid_init(TALLOC_CTX *ctx) +{ + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, + "autorid", &autorid_methods); +} diff --git a/source3/winbindd/idmap_autorid_tdb.c b/source3/winbindd/idmap_autorid_tdb.c new file mode 100644 index 0000000..6c76764 --- /dev/null +++ b/source3/winbindd/idmap_autorid_tdb.c @@ -0,0 +1,1269 @@ +/* + * idmap_autorid_tdb: This file contains common code used by + * idmap_autorid and net idmap autorid utilities. The common + * code provides functions for performing various operations + * on autorid.tdb + * + * Copyright (C) Christian Ambach, 2010-2012 + * Copyright (C) Atul Kulkarni, 2013 + * Copyright (C) Michael Adam, 2012-2013 + * + * 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 "idmap_autorid_tdb.h" +#include "../libcli/security/dom_sid.h" +#include "lib/util/string_wrappers.h" + +/** + * Build the database keystring for getting a range + * belonging to a domain sid and a range index. + */ +static void idmap_autorid_build_keystr(const char *domsid, + uint32_t domain_range_index, + fstring keystr) +{ + if (domain_range_index > 0) { + fstr_sprintf(keystr, "%s#%"PRIu32, + domsid, domain_range_index); + } else { + fstrcpy(keystr, domsid); + } +} + +static char *idmap_autorid_build_keystr_talloc(TALLOC_CTX *mem_ctx, + const char *domsid, + uint32_t domain_range_index) +{ + char *keystr; + + if (domain_range_index > 0) { + keystr = talloc_asprintf(mem_ctx, "%s#%"PRIu32, domsid, + domain_range_index); + } else { + keystr = talloc_strdup(mem_ctx, domsid); + } + + return keystr; +} + + +static bool idmap_autorid_validate_sid(const char *sid) +{ + struct dom_sid ignore; + if (sid == NULL) { + return false; + } + + if (strcmp(sid, ALLOC_RANGE) == 0) { + return true; + } + + return dom_sid_parse(sid, &ignore); +} + +struct idmap_autorid_addrange_ctx { + struct autorid_range_config *range; + bool acquire; +}; + +static NTSTATUS idmap_autorid_addrange_action(struct db_context *db, + void *private_data) +{ + struct idmap_autorid_addrange_ctx *ctx; + uint32_t requested_rangenum, stored_rangenum; + struct autorid_range_config *range; + bool acquire; + NTSTATUS ret; + uint32_t hwm; + char *numstr; + struct autorid_global_config globalcfg = {0}; + fstring keystr; + uint32_t increment; + TALLOC_CTX *mem_ctx = NULL; + + ctx = (struct idmap_autorid_addrange_ctx *)private_data; + range = ctx->range; + acquire = ctx->acquire; + requested_rangenum = range->rangenum; + + if (db == NULL) { + DEBUG(3, ("Invalid database argument: NULL\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (range == NULL) { + DEBUG(3, ("Invalid range argument: NULL\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(10, ("Adding new range for domain %s " + "(domain_range_index=%"PRIu32")\n", + range->domsid, range->domain_range_index)); + + if (!idmap_autorid_validate_sid(range->domsid)) { + DEBUG(3, ("Invalid SID: %s\n", range->domsid)); + return NT_STATUS_INVALID_PARAMETER; + } + + idmap_autorid_build_keystr(range->domsid, range->domain_range_index, + keystr); + + ret = dbwrap_fetch_uint32_bystring(db, keystr, &stored_rangenum); + + if (NT_STATUS_IS_OK(ret)) { + /* entry is already present*/ + if (acquire) { + DEBUG(10, ("domain range already allocated - " + "Not adding!\n")); + + ret = idmap_autorid_loadconfig(db, &globalcfg); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Fatal error while fetching " + "configuration: %s\n", + nt_errstr(ret))); + goto error; + } + + range->rangenum = stored_rangenum; + range->low_id = globalcfg.minvalue + + range->rangenum * globalcfg.rangesize; + range->high_id = + range->low_id + globalcfg.rangesize - 1; + + return NT_STATUS_OK; + } + + if (stored_rangenum != requested_rangenum) { + DEBUG(1, ("Error: requested rangenumber (%u) differs " + "from stored one (%u).\n", + requested_rangenum, stored_rangenum)); + return NT_STATUS_UNSUCCESSFUL; + } + + DEBUG(10, ("Note: stored range agrees with requested " + "one - ok\n")); + return NT_STATUS_OK; + } + + /* fetch the current HWM */ + ret = dbwrap_fetch_uint32_bystring(db, HWM, &hwm); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Fatal error while fetching current " + "HWM value: %s\n", nt_errstr(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + + mem_ctx = talloc_stackframe(); + + ret = idmap_autorid_loadconfig(db, &globalcfg); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Fatal error while fetching configuration: %s\n", + nt_errstr(ret))); + goto error; + } + + if (acquire) { + /* + * automatically acquire the next range + */ + requested_rangenum = hwm; + } + + if (requested_rangenum >= globalcfg.maxranges) { + DBG_WARNING("Not enough ranges available: New range %u can't " + "be allocated. Consider increasing the range " + "[%u-%u] by %u.\n", + requested_rangenum, + globalcfg.minvalue, + globalcfg.minvalue + + (globalcfg.maxranges * globalcfg.rangesize), + globalcfg.rangesize); + ret = NT_STATUS_NO_MEMORY; + goto error; + } + + /* + * Check that it is not yet taken. + * If the range is requested and < HWM, we need + * to check anyways, and otherwise, we also better + * check in order to prevent further corruption + * in case the db has been externally modified. + */ + + numstr = talloc_asprintf(mem_ctx, "%u", requested_rangenum); + if (!numstr) { + DEBUG(1, ("Talloc failed!\n")); + ret = NT_STATUS_NO_MEMORY; + goto error; + } + + if (dbwrap_exists(db, string_term_tdb_data(numstr))) { + DEBUG(1, ("Requested range '%s' is already in use.\n", numstr)); + + if (requested_rangenum < hwm) { + ret = NT_STATUS_INVALID_PARAMETER; + } else { + ret = NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + goto error; + } + + if (requested_rangenum >= hwm) { + /* + * requested or automatic range >= HWM: + * increment the HWM. + */ + + /* HWM always contains current max range + 1 */ + increment = requested_rangenum + 1 - hwm; + + /* increase the HWM */ + ret = dbwrap_change_uint32_atomic_bystring(db, HWM, &hwm, + increment); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Fatal error while incrementing the HWM " + "value in the database: %s\n", + nt_errstr(ret))); + goto error; + } + } + + /* + * store away the new mapping in both directions + */ + + ret = dbwrap_store_uint32_bystring(db, keystr, requested_rangenum); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Fatal error while storing new " + "domain->range assignment: %s\n", nt_errstr(ret))); + goto error; + } + + numstr = talloc_asprintf(mem_ctx, "%u", requested_rangenum); + if (!numstr) { + ret = NT_STATUS_NO_MEMORY; + goto error; + } + + ret = dbwrap_store_bystring(db, numstr, + string_term_tdb_data(keystr), TDB_INSERT); + + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Fatal error while storing new " + "domain->range assignment: %s\n", nt_errstr(ret))); + goto error; + } + + DEBUG(5, ("%s new range #%d for domain %s " + "(domain_range_index=%"PRIu32")\n", + (acquire?"Acquired":"Stored"), + requested_rangenum, keystr, + range->domain_range_index)); + + range->rangenum = requested_rangenum; + + range->low_id = globalcfg.minvalue + + range->rangenum * globalcfg.rangesize; + range->high_id = range->low_id + globalcfg.rangesize - 1; + + ret = NT_STATUS_OK; + +error: + talloc_free(mem_ctx); + return ret; +} + +static NTSTATUS idmap_autorid_addrange(struct db_context *db, + struct autorid_range_config *range, + bool acquire) +{ + NTSTATUS status; + struct idmap_autorid_addrange_ctx ctx; + + ctx.acquire = acquire; + ctx.range = range; + + status = dbwrap_trans_do(db, idmap_autorid_addrange_action, &ctx); + return status; +} + +NTSTATUS idmap_autorid_setrange(struct db_context *db, + const char *domsid, + uint32_t domain_range_index, + uint32_t rangenum) +{ + NTSTATUS status; + struct autorid_range_config range; + + ZERO_STRUCT(range); + fstrcpy(range.domsid, domsid); + range.domain_range_index = domain_range_index; + range.rangenum = rangenum; + + status = idmap_autorid_addrange(db, &range, false); + return status; +} + +NTSTATUS idmap_autorid_acquire_range(struct db_context *db, + struct autorid_range_config *range) +{ + return idmap_autorid_addrange(db, range, true); +} + +static NTSTATUS idmap_autorid_getrange_int(struct db_context *db, + struct autorid_range_config *range) +{ + NTSTATUS status = NT_STATUS_INVALID_PARAMETER; + struct autorid_global_config globalcfg = {0}; + fstring keystr; + + if (db == NULL || range == NULL) { + DEBUG(3, ("Invalid arguments received\n")); + goto done; + } + + if (!idmap_autorid_validate_sid(range->domsid)) { + DEBUG(3, ("Invalid SID: '%s'\n", range->domsid)); + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + idmap_autorid_build_keystr(range->domsid, range->domain_range_index, + keystr); + + DEBUG(10, ("reading domain range for key %s\n", keystr)); + status = dbwrap_fetch_uint32_bystring(db, keystr, &(range->rangenum)); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to read database record for key '%s': %s\n", + keystr, nt_errstr(status))); + goto done; + } + + status = idmap_autorid_loadconfig(db, &globalcfg); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to read global configuration\n")); + goto done; + } + range->low_id = globalcfg.minvalue + + range->rangenum * globalcfg.rangesize; + range->high_id = range->low_id + globalcfg.rangesize - 1; +done: + return status; +} + +NTSTATUS idmap_autorid_getrange(struct db_context *db, + const char *domsid, + uint32_t domain_range_index, + uint32_t *rangenum, + uint32_t *low_id) +{ + NTSTATUS status; + struct autorid_range_config range; + + if (rangenum == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + ZERO_STRUCT(range); + fstrcpy(range.domsid, domsid); + range.domain_range_index = domain_range_index; + + status = idmap_autorid_getrange_int(db, &range); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *rangenum = range.rangenum; + + if (low_id != NULL) { + *low_id = range.low_id; + } + + return NT_STATUS_OK; +} + +NTSTATUS idmap_autorid_get_domainrange(struct db_context *db, + struct autorid_range_config *range, + bool read_only) +{ + NTSTATUS ret; + + ret = idmap_autorid_getrange_int(db, range); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(10, ("Failed to read range config for '%s': %s\n", + range->domsid, nt_errstr(ret))); + if (read_only) { + DEBUG(10, ("Not allocating new range for '%s' because " + "read-only is enabled.\n", range->domsid)); + return NT_STATUS_NOT_FOUND; + } + + ret = idmap_autorid_acquire_range(db, range); + } + + DEBUG(10, ("Using range #%d for domain %s " + "(domain_range_index=%"PRIu32", low_id=%"PRIu32")\n", + range->rangenum, range->domsid, range->domain_range_index, + range->low_id)); + + return ret; +} + +/* initialize the given HWM to 0 if it does not exist yet */ +static NTSTATUS idmap_autorid_init_hwm_action(struct db_context *db, + void *private_data) +{ + NTSTATUS status; + uint32_t hwmval; + const char *hwm; + + hwm = (char *)private_data; + + status = dbwrap_fetch_uint32_bystring(db, hwm, &hwmval); + if (NT_STATUS_IS_OK(status)) { + DEBUG(1, ("HWM (%s) already initialized in autorid database " + "(value %"PRIu32").\n", hwm, hwmval)); + return NT_STATUS_OK; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + DEBUG(0, ("Error fetching HWM (%s) from autorid " + "database: %s\n", hwm, nt_errstr(status))); + return status; + } + + status = dbwrap_trans_store_uint32_bystring(db, hwm, 0); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Error storing HWM (%s) in autorid database: %s\n", + hwm, nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +NTSTATUS idmap_autorid_init_hwm(struct db_context *db, const char *hwm) +{ + NTSTATUS status; + uint32_t hwmval; + + status = dbwrap_fetch_uint32_bystring(db, hwm, &hwmval); + if (NT_STATUS_IS_OK(status)) { + DEBUG(1, ("HWM (%s) already initialized in autorid database " + "(value %"PRIu32").\n", hwm, hwmval)); + return NT_STATUS_OK; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + DEBUG(0, ("unable to fetch HWM (%s) from autorid " + "database: %s\n", hwm, nt_errstr(status))); + return status; + } + + status = dbwrap_trans_do(db, idmap_autorid_init_hwm_action, + discard_const(hwm)); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Error initializing HWM (%s) in autorid database: " + "%s\n", hwm, nt_errstr(status))); + return NT_STATUS_INTERNAL_DB_ERROR; + } + + DEBUG(1, ("Initialized HWM (%s) in autorid database.\n", hwm)); + + return NT_STATUS_OK; +} + +/* + * Delete a domain#index <-> range mapping from the database. + * The mapping is specified by the sid and index. + * If force == true, invalid mapping records are deleted as far + * as possible, otherwise they are left untouched. + */ + +struct idmap_autorid_delete_range_by_sid_ctx { + const char *domsid; + uint32_t domain_range_index; + bool force; +}; + +static NTSTATUS idmap_autorid_delete_range_by_sid_action(struct db_context *db, + void *private_data) +{ + struct idmap_autorid_delete_range_by_sid_ctx *ctx = + (struct idmap_autorid_delete_range_by_sid_ctx *)private_data; + const char *domsid; + uint32_t domain_range_index; + uint32_t rangenum; + char *keystr; + char *range_keystr; + TDB_DATA data; + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + bool is_valid_range_mapping = true; + bool force; + + domsid = ctx->domsid; + domain_range_index = ctx->domain_range_index; + force = ctx->force; + + keystr = idmap_autorid_build_keystr_talloc(frame, domsid, + domain_range_index); + if (keystr == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + status = dbwrap_fetch_uint32_bystring(db, keystr, &rangenum); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + range_keystr = talloc_asprintf(frame, "%"PRIu32, rangenum); + if (range_keystr == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + status = dbwrap_fetch_bystring(db, frame, range_keystr, &data); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + DEBUG(1, ("Incomplete mapping %s -> %s: no backward mapping\n", + keystr, range_keystr)); + is_valid_range_mapping = false; + } else if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Error fetching reverse mapping for %s -> %s: %s\n", + keystr, range_keystr, nt_errstr(status))); + goto done; + } else if (strncmp((const char *)data.dptr, keystr, strlen(keystr)) + != 0) + { + DEBUG(1, ("Invalid mapping: %s -> %s -> %s\n", + keystr, range_keystr, (const char *)data.dptr)); + is_valid_range_mapping = false; + } + + if (!is_valid_range_mapping && !force) { + DEBUG(10, ("Not deleting invalid mapping, since not in force " + "mode.\n")); + status = NT_STATUS_FILE_INVALID; + goto done; + } + + status = dbwrap_delete_bystring(db, keystr); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Deletion of '%s' failed: %s\n", + keystr, nt_errstr(status))); + goto done; + } + + if (!is_valid_range_mapping) { + goto done; + } + + status = dbwrap_delete_bystring(db, range_keystr); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Deletion of '%s' failed: %s\n", + range_keystr, nt_errstr(status))); + goto done; + } + + DEBUG(10, ("Deleted range mapping %s <--> %s\n", keystr, + range_keystr)); + +done: + TALLOC_FREE(frame); + return status; +} + +NTSTATUS idmap_autorid_delete_range_by_sid(struct db_context *db, + const char *domsid, + uint32_t domain_range_index, + bool force) +{ + NTSTATUS status; + struct idmap_autorid_delete_range_by_sid_ctx ctx; + + ctx.domain_range_index = domain_range_index; + ctx.domsid = domsid; + ctx.force = force; + + status = dbwrap_trans_do(db, idmap_autorid_delete_range_by_sid_action, + &ctx); + return status; +} + +/* + * Delete a domain#index <-> range mapping from the database. + * The mapping is specified by the range number. + * If force == true, invalid mapping records are deleted as far + * as possible, otherwise they are left untouched. + */ +struct idmap_autorid_delete_range_by_num_ctx { + uint32_t rangenum; + bool force; +}; + +static NTSTATUS idmap_autorid_delete_range_by_num_action(struct db_context *db, + void *private_data) +{ + struct idmap_autorid_delete_range_by_num_ctx *ctx = + (struct idmap_autorid_delete_range_by_num_ctx *)private_data; + uint32_t rangenum; + char *keystr = NULL; + char *range_keystr; + TDB_DATA val; + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + bool is_valid_range_mapping = true; + bool force; + + rangenum = ctx->rangenum; + force = ctx->force; + + range_keystr = talloc_asprintf(frame, "%"PRIu32, rangenum); + if (range_keystr == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + ZERO_STRUCT(val); + + status = dbwrap_fetch_bystring(db, frame, range_keystr, &val); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + DEBUG(10, ("Did not find range '%s' in database.\n", + range_keystr)); + goto done; + } else if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("Error fetching rang key: %s\n", nt_errstr(status))); + goto done; + } + + if (val.dptr == NULL) { + DEBUG(1, ("Invalid mapping: %s -> empty value\n", + range_keystr)); + is_valid_range_mapping = false; + } else { + uint32_t reverse_rangenum = 0; + + keystr = (char *)val.dptr; + + status = dbwrap_fetch_uint32_bystring(db, keystr, + &reverse_rangenum); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + DEBUG(1, ("Incomplete mapping %s -> %s: " + "no backward mapping\n", + range_keystr, keystr)); + is_valid_range_mapping = false; + } else if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Error fetching reverse mapping for " + "%s -> %s: %s\n", + range_keystr, keystr, nt_errstr(status))); + goto done; + } else if (rangenum != reverse_rangenum) { + is_valid_range_mapping = false; + } + } + + if (!is_valid_range_mapping && !force) { + DEBUG(10, ("Not deleting invalid mapping, since not in force " + "mode.\n")); + status = NT_STATUS_FILE_INVALID; + goto done; + } + + status = dbwrap_delete_bystring(db, range_keystr); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Deletion of '%s' failed: %s\n", + range_keystr, nt_errstr(status))); + goto done; + } + + if (!is_valid_range_mapping) { + goto done; + } + + status = dbwrap_delete_bystring(db, keystr); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Deletion of '%s' failed: %s\n", + keystr, nt_errstr(status))); + goto done; + } + + DEBUG(10, ("Deleted range mapping %s <--> %s\n", range_keystr, + keystr)); + +done: + talloc_free(frame); + return status; +} + +NTSTATUS idmap_autorid_delete_range_by_num(struct db_context *db, + uint32_t rangenum, + bool force) +{ + NTSTATUS status; + struct idmap_autorid_delete_range_by_num_ctx ctx; + + ctx.rangenum = rangenum; + ctx.force = force; + + status = dbwrap_trans_do(db, idmap_autorid_delete_range_by_num_action, + &ctx); + return status; +} + +/** + * Open and possibly create the database. + */ +NTSTATUS idmap_autorid_db_open(const char *path, + TALLOC_CTX *mem_ctx, + struct db_context **db) +{ + if (*db != NULL) { + /* its already open */ + return NT_STATUS_OK; + } + + /* Open idmap repository */ + *db = db_open(mem_ctx, path, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644, + DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE); + + if (*db == NULL) { + DEBUG(0, ("Unable to open idmap_autorid database '%s'\n", path)); + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +/** + * Initialize the high watermark records in the database. + */ +NTSTATUS idmap_autorid_init_hwms(struct db_context *db) +{ + NTSTATUS status; + + status = idmap_autorid_init_hwm(db, HWM); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = idmap_autorid_init_hwm(db, ALLOC_HWM_UID); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = idmap_autorid_init_hwm(db, ALLOC_HWM_GID); + + return status; +} + +NTSTATUS idmap_autorid_db_init(const char *path, + TALLOC_CTX *mem_ctx, + struct db_context **db) +{ + NTSTATUS status; + + status = idmap_autorid_db_open(path, mem_ctx, db); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = idmap_autorid_init_hwms(*db); + return status; +} + + + +struct idmap_autorid_fetch_config_state { + TALLOC_CTX *mem_ctx; + char *configstr; +}; + +static void idmap_autorid_config_parser(TDB_DATA key, TDB_DATA value, + void *private_data) +{ + struct idmap_autorid_fetch_config_state *state; + + state = (struct idmap_autorid_fetch_config_state *)private_data; + + /* + * strndup because we have non-nullterminated strings in the db + */ + state->configstr = talloc_strndup( + state->mem_ctx, (const char *)value.dptr, value.dsize); +} + +NTSTATUS idmap_autorid_getconfigstr(struct db_context *db, TALLOC_CTX *mem_ctx, + char **result) +{ + TDB_DATA key; + NTSTATUS status; + struct idmap_autorid_fetch_config_state state; + + if (result == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + key = string_term_tdb_data(CONFIGKEY); + + state.mem_ctx = mem_ctx; + state.configstr = NULL; + + status = dbwrap_parse_record(db, key, idmap_autorid_config_parser, + &state); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Error while retrieving config: %s\n", + nt_errstr(status))); + return status; + } + + if (state.configstr == NULL) { + DEBUG(1, ("Error while retrieving config\n")); + return NT_STATUS_NO_MEMORY; + } + + DEBUG(5, ("found CONFIG: %s\n", state.configstr)); + + *result = state.configstr; + return NT_STATUS_OK; +} + +bool idmap_autorid_parse_configstr(const char *configstr, + struct autorid_global_config *cfg) +{ + unsigned long minvalue, rangesize, maxranges; + + if (sscanf(configstr, + "minvalue:%lu rangesize:%lu maxranges:%lu", + &minvalue, &rangesize, &maxranges) != 3) { + DEBUG(1, + ("Found invalid configuration data. " + "Creating new config\n")); + return false; + } + + cfg->minvalue = minvalue; + cfg->rangesize = rangesize; + cfg->maxranges = maxranges; + + return true; +} + +NTSTATUS idmap_autorid_loadconfig(struct db_context *db, + struct autorid_global_config *result) +{ + struct autorid_global_config cfg = {0}; + NTSTATUS status; + bool ok; + char *configstr = NULL; + + if (result == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = idmap_autorid_getconfigstr(db, db, &configstr); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ok = idmap_autorid_parse_configstr(configstr, &cfg); + TALLOC_FREE(configstr); + if (!ok) { + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(10, ("Loaded previously stored configuration " + "minvalue:%d rangesize:%d\n", + cfg.minvalue, cfg.rangesize)); + + *result = cfg; + + return NT_STATUS_OK; +} + +NTSTATUS idmap_autorid_saveconfig(struct db_context *db, + struct autorid_global_config *cfg) +{ + + struct autorid_global_config storedconfig = {0}; + NTSTATUS status = NT_STATUS_INVALID_PARAMETER; + TDB_DATA data; + char *cfgstr; + uint32_t hwm; + TALLOC_CTX *frame = talloc_stackframe(); + + DEBUG(10, ("New configuration provided for storing is " + "minvalue:%d rangesize:%d maxranges:%d\n", + cfg->minvalue, cfg->rangesize, cfg->maxranges)); + + if (cfg->rangesize < 2000) { + DEBUG(1, ("autorid rangesize must be at least 2000\n")); + goto done; + } + + if (cfg->maxranges == 0) { + DEBUG(1, ("An autorid maxranges value of 0 is invalid. " + "Must have at least one range available.\n")); + goto done; + } + + status = idmap_autorid_loadconfig(db, &storedconfig); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + DEBUG(5, ("No configuration found. Storing initial " + "configuration.\n")); + storedconfig = *cfg; + } else if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Error loading configuration: %s\n", + nt_errstr(status))); + goto done; + } + + /* did the minimum value or rangesize change? */ + if ((storedconfig.minvalue != cfg->minvalue) || + (storedconfig.rangesize != cfg->rangesize)) + { + DEBUG(1, ("New configuration values for rangesize or " + "minimum uid value conflict with previously " + "used values! Not storing new config.\n")); + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + status = dbwrap_fetch_uint32_bystring(db, HWM, &hwm); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Fatal error while fetching current " + "HWM value: %s\n", nt_errstr(status))); + status = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + /* + * has the highest uid value been reduced to setting that is not + * sufficient any more for already existing ranges? + */ + if (hwm > cfg->maxranges) { + DEBUG(1, ("New upper uid limit is too low to cover " + "existing mappings! Not storing new config.\n")); + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + cfgstr = + talloc_asprintf(frame, + "minvalue:%u rangesize:%u maxranges:%u", + cfg->minvalue, cfg->rangesize, cfg->maxranges); + + if (cfgstr == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + data = string_tdb_data(cfgstr); + + status = dbwrap_trans_store_bystring(db, CONFIGKEY, data, TDB_REPLACE); + +done: + TALLOC_FREE(frame); + return status; +} + +NTSTATUS idmap_autorid_saveconfigstr(struct db_context *db, + const char *configstr) +{ + bool ok; + NTSTATUS status; + struct autorid_global_config cfg; + + ok = idmap_autorid_parse_configstr(configstr, &cfg); + if (!ok) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = idmap_autorid_saveconfig(db, &cfg); + return status; +} + + +/* + * iteration: Work on all range mappings for a given domain + */ + +struct domain_range_visitor_ctx { + const char *domsid; + NTSTATUS (*fn)(struct db_context *db, + const char *domsid, + uint32_t index, + uint32_t rangenum, + void *private_data); + void *private_data; + int count; /* number of records worked on */ +}; + +static int idmap_autorid_visit_domain_range(struct db_record *rec, + void *private_data) +{ + struct domain_range_visitor_ctx *vi; + char *domsid; + char *sep; + uint32_t range_index = 0; + uint32_t rangenum = 0; + TDB_DATA key, value; + NTSTATUS status; + int ret = 0; + struct db_context *db; + + vi = talloc_get_type_abort(private_data, + struct domain_range_visitor_ctx); + + key = dbwrap_record_get_key(rec); + + /* + * split string "<sid>[#<index>]" into sid string and index number + */ + + domsid = (char *)key.dptr; + + DEBUG(10, ("idmap_autorid_visit_domain_range: visiting key '%s'\n", + domsid)); + + sep = strrchr(domsid, '#'); + if (sep != NULL) { + char *index_str; + *sep = '\0'; + index_str = sep+1; + if (sscanf(index_str, "%"SCNu32, &range_index) != 1) { + DEBUG(10, ("Found separator '#' but '%s' is not a " + "valid range index. Skipping record\n", + index_str)); + goto done; + } + } + + if (!idmap_autorid_validate_sid(domsid)) { + DEBUG(10, ("String '%s' is not a valid sid. " + "Skipping record.\n", domsid)); + goto done; + } + + if ((vi->domsid != NULL) && (strcmp(domsid, vi->domsid) != 0)) { + DEBUG(10, ("key sid '%s' does not match requested sid '%s'.\n", + domsid, vi->domsid)); + goto done; + } + + value = dbwrap_record_get_value(rec); + + if (value.dsize != sizeof(uint32_t)) { + /* it might be a mapping of a well known sid */ + DEBUG(10, ("value size %u != sizeof(uint32_t) for sid '%s', " + "skipping.\n", (unsigned)value.dsize, vi->domsid)); + goto done; + } + + rangenum = IVAL(value.dptr, 0); + + db = dbwrap_record_get_db(rec); + + status = vi->fn(db, domsid, range_index, rangenum, vi->private_data); + if (!NT_STATUS_IS_OK(status)) { + ret = -1; + goto done; + } + + vi->count++; + ret = 0; + +done: + return ret; +} + +static NTSTATUS idmap_autorid_iterate_domain_ranges_int(struct db_context *db, + const char *domsid, + NTSTATUS (*fn)(struct db_context *db, + const char *domsid, + uint32_t index, + uint32_t rangnum, + void *private_data), + void *private_data, + int *count, + NTSTATUS (*traverse)(struct db_context *db, + int (*f)(struct db_record *, void *), + void *private_data, + int *count)) +{ + NTSTATUS status; + struct domain_range_visitor_ctx *vi; + TALLOC_CTX *frame = talloc_stackframe(); + + if (domsid == NULL) { + DEBUG(10, ("No sid provided, operating on all ranges\n")); + } + + if (fn == NULL) { + DEBUG(1, ("Error: missing visitor callback\n")); + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + vi = talloc_zero(frame, struct domain_range_visitor_ctx); + if (vi == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + vi->domsid = domsid; + vi->fn = fn; + vi->private_data = private_data; + + status = traverse(db, idmap_autorid_visit_domain_range, vi, NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (count != NULL) { + *count = vi->count; + } + +done: + talloc_free(frame); + return status; +} + +NTSTATUS idmap_autorid_iterate_domain_ranges(struct db_context *db, + const char *domsid, + NTSTATUS (*fn)(struct db_context *db, + const char *domsid, + uint32_t index, + uint32_t rangenum, + void *private_data), + void *private_data, + int *count) +{ + NTSTATUS status; + + status = idmap_autorid_iterate_domain_ranges_int(db, + domsid, + fn, + private_data, + count, + dbwrap_traverse); + + return status; +} + + +NTSTATUS idmap_autorid_iterate_domain_ranges_read(struct db_context *db, + const char *domsid, + NTSTATUS (*fn)(struct db_context *db, + const char *domsid, + uint32_t index, + uint32_t rangenum, + void *count), + void *private_data, + int *count) +{ + NTSTATUS status; + + status = idmap_autorid_iterate_domain_ranges_int(db, + domsid, + fn, + private_data, + count, + dbwrap_traverse_read); + + return status; +} + + +/* + * Delete all ranges configured for a given domain + */ + +struct delete_domain_ranges_visitor_ctx { + bool force; +}; + +static NTSTATUS idmap_autorid_delete_domain_ranges_visitor( + struct db_context *db, + const char *domsid, + uint32_t domain_range_index, + uint32_t rangenum, + void *private_data) +{ + struct delete_domain_ranges_visitor_ctx *ctx; + NTSTATUS status; + + ctx = (struct delete_domain_ranges_visitor_ctx *)private_data; + + status = idmap_autorid_delete_range_by_sid( + db, domsid, domain_range_index, ctx->force); + return status; +} + +struct idmap_autorid_delete_domain_ranges_ctx { + const char *domsid; + bool force; + int count; /* output: count records operated on */ +}; + +static NTSTATUS idmap_autorid_delete_domain_ranges_action(struct db_context *db, + void *private_data) +{ + struct idmap_autorid_delete_domain_ranges_ctx *ctx; + struct delete_domain_ranges_visitor_ctx visitor_ctx; + int count; + NTSTATUS status; + + ctx = (struct idmap_autorid_delete_domain_ranges_ctx *)private_data; + + ZERO_STRUCT(visitor_ctx); + visitor_ctx.force = ctx->force; + + status = idmap_autorid_iterate_domain_ranges(db, + ctx->domsid, + idmap_autorid_delete_domain_ranges_visitor, + &visitor_ctx, + &count); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ctx->count = count; + + return NT_STATUS_OK; +} + +NTSTATUS idmap_autorid_delete_domain_ranges(struct db_context *db, + const char *domsid, + bool force, + int *count) +{ + NTSTATUS status; + struct idmap_autorid_delete_domain_ranges_ctx ctx; + + ZERO_STRUCT(ctx); + ctx.domsid = domsid; + ctx.force = force; + + status = dbwrap_trans_do(db, idmap_autorid_delete_domain_ranges_action, + &ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *count = ctx.count; + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/idmap_hash/idmap_hash.c b/source3/winbindd/idmap_hash/idmap_hash.c new file mode 100644 index 0000000..c2e835c --- /dev/null +++ b/source3/winbindd/idmap_hash/idmap_hash.c @@ -0,0 +1,504 @@ +/* + * 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.h" +#include "idmap_hash.h" +#include "ads.h" +#include "nss_info.h" +#include "../libcli/security/dom_sid.h" +#include "libsmb/samlogon_cache.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +struct sid_hash_table { + struct dom_sid *sid; +}; + +/********************************************************************* + Hash a domain SID (S-1-5-12-aaa-bbb-ccc) to a 12bit number + ********************************************************************/ + +static uint32_t hash_domain_sid(const struct 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)) & 0x00000FFF; + + /* return a 12-bit hash value */ + + return hash; +} + +/********************************************************************* + Hash a Relative ID to a 19 bit number + ********************************************************************/ + +static uint32_t hash_rid(uint32_t rid) +{ + /* + * 19 bits for the rid which allows us to support + * the first 50K 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 + * + * This will generate a 31 bit number out of + * 12 bit domain and 19 bit 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 idmap_hash_initialize(struct idmap_domain *dom) +{ + struct sid_hash_table *hashed_domains; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + struct winbindd_tdc_domain *dom_list = NULL; + size_t num_domains = 0; + size_t i; + + DBG_ERR("The idmap_hash module is deprecated and should not be used. " + "Please migrate to a different plugin. This module will be " + "removed in a future version of Samba\n"); + + if (!strequal(dom->name, "*")) { + DBG_ERR("Error: idmap_hash configured for domain '%s'. " + "But the hash module can only be used for the default " + "idmap configuration.\n", dom->name); + return NT_STATUS_INVALID_PARAMETER; + } + + 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(dom, 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++) { + struct dom_sid_buf buf; + uint32_t hash; + + if (is_null_sid(&dom_list[i].sid)) + continue; + + /* + * Check if the domain from the list is not already configured + * to use another idmap backend. Not checking this makes the + * idmap_hash module map IDs for *all* domains implicitly. This + * is quite dangerous in setups that use multiple idmap + * configurations. + */ + + if (domain_has_idmap_config(dom_list[i].domain_name)) { + continue; + } + + if ((hash = hash_domain_sid(&dom_list[i].sid)) == 0) + continue; + + DBG_INFO("Adding %s (%s) -> %d\n", + dom_list[i].domain_name, + dom_sid_str_buf(&dom_list[i].sid, &buf), + hash); + + hashed_domains[hash].sid = talloc(hashed_domains, struct dom_sid); + sid_copy(hashed_domains[hash].sid, &dom_list[i].sid); + } + + dom->private_data = hashed_domains; + +done: + return nt_status; +} + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS idmap_hash_id_to_sid(struct sid_hash_table *hashed_domains, + struct idmap_domain *dom, + struct id_map *id) +{ + uint32_t h_domain = 0, h_rid = 0; + + id->status = ID_UNMAPPED; + + separate_hashes(id->xid.id, &h_domain, &h_rid); + + /* + * If the domain hash doesn't find a SID in the table, + * skip it + */ + if (hashed_domains[h_domain].sid == NULL) { + /* keep ID_UNMAPPED */ + return NT_STATUS_OK; + } + + id->xid.type = ID_TYPE_BOTH; + sid_compose(id->sid, hashed_domains[h_domain].sid, h_rid); + id->status = ID_MAPPED; + + return NT_STATUS_OK; +} + +static NTSTATUS unixids_to_sids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct sid_hash_table *hashed_domains = talloc_get_type_abort( + dom->private_data, struct sid_hash_table); + size_t i; + size_t num_tomap = 0; + size_t num_mapped = 0; + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + num_tomap++; + } + + for (i=0; ids[i]; i++) { + NTSTATUS ret; + + ret = idmap_hash_id_to_sid(hashed_domains, dom, ids[i]); + if (!NT_STATUS_IS_OK(ret)) { + /* some fatal error occurred, log it */ + DBG_NOTICE("Unexpected error resolving an ID " + "(%d): %s\n", ids[i]->xid.id, + nt_errstr(ret)); + return ret; + } + + if (ids[i]->status == ID_MAPPED) { + num_mapped++; + } + } + + if (num_tomap == num_mapped) { + return NT_STATUS_OK; + } else if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + + return STATUS_SOME_UNMAPPED; +} + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS idmap_hash_sid_to_id(struct sid_hash_table *hashed_domains, + struct idmap_domain *dom, + struct id_map *id) +{ + struct dom_sid sid; + uint32_t rid; + uint32_t h_domain, h_rid; + + id->status = ID_UNMAPPED; + + sid_copy(&sid, id->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 == 0) { + /* keep ID_UNMAPPED */ + return NT_STATUS_OK; + } + if (h_rid == 0) { + /* keep ID_UNMAPPED */ + return NT_STATUS_OK; + } + + /* + * If the domain hash already exists find a SID in the table, + * just return the mapping. + */ + if (hashed_domains[h_domain].sid != NULL) { + goto return_mapping; + } + + /* + * Check of last resort: A domain is valid if a user from that + * domain has recently logged in. The samlogon_cache these + * days also stores the domain sid. + */ + if (netsamlogon_cache_have(&sid)) { + /* + * The domain is valid, so we'll + * remember it in order to + * allow reverse mappings to work. + */ + goto remember_domain; + } + + if (id->xid.type == ID_TYPE_NOT_SPECIFIED) { + /* + * idmap_hash used to bounce back the requested type, + * which was ID_TYPE_UID, ID_TYPE_GID or + * ID_TYPE_NOT_SPECIFIED before as the winbindd parent + * always used a lookupsids. When the lookupsids + * failed because of an unknown domain, the idmap child + * weren't requested at all and the caller sees + * ID_TYPE_NOT_SPECIFIED. + * + * Now that the winbindd parent will pass ID_TYPE_BOTH + * in order to indicate that the domain exists. + * We should ask the parent to fallback to lookupsids + * if the domain is not known yet. + */ + id->status = ID_REQUIRE_TYPE; + return NT_STATUS_OK; + } + + /* + * Now we're sure the domain exist, remember + * the domain in order to return reverse mappings + * in future. + */ +remember_domain: + hashed_domains[h_domain].sid = dom_sid_dup(hashed_domains, &sid); + if (hashed_domains[h_domain].sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * idmap_hash used to bounce back the requested type, + * which was ID_TYPE_UID, ID_TYPE_GID or + * ID_TYPE_NOT_SPECIFIED before as the winbindd parent + * always used a lookupsids. + * + * This module should have supported ID_TYPE_BOTH since + * samba-4.1.0, similar to idmap_rid and idmap_autorid. + * + * Now that the winbindd parent will pass ID_TYPE_BOTH + * in order to indicate that the domain exists, it's + * better to always return ID_TYPE_BOTH instead of a + * random mix of ID_TYPE_UID, ID_TYPE_GID or + * ID_TYPE_BOTH. + */ +return_mapping: + id->xid.type = ID_TYPE_BOTH; + id->xid.id = combine_hashes(h_domain, h_rid); + id->status = ID_MAPPED; + + return NT_STATUS_OK; +} + +static NTSTATUS sids_to_unixids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct sid_hash_table *hashed_domains = talloc_get_type_abort( + dom->private_data, struct sid_hash_table); + size_t i; + size_t num_tomap = 0; + size_t num_mapped = 0; + size_t num_required = 0; + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + num_tomap++; + } + + for (i=0; ids[i]; i++) { + NTSTATUS ret; + + ret = idmap_hash_sid_to_id(hashed_domains, dom, ids[i]); + if (!NT_STATUS_IS_OK(ret)) { + struct dom_sid_buf buf; + /* some fatal error occurred, log it */ + DBG_NOTICE("Unexpected error resolving a SID " + "(%s): %s\n", + dom_sid_str_buf(ids[i]->sid, &buf), + nt_errstr(ret)); + return ret; + } + + if (ids[i]->status == ID_MAPPED) { + num_mapped++; + } + if (ids[i]->status == ID_REQUIRE_TYPE) { + num_required++; + } + } + + if (num_tomap == num_mapped) { + return NT_STATUS_OK; + } else if (num_required > 0) { + return STATUS_SOME_UNMAPPED; + } else if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + + return STATUS_SOME_UNMAPPED; +} + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS nss_hash_init(struct nss_domain_entry *e ) +{ + return NT_STATUS_OK; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_hash_map_to_alias(TALLOC_CTX *mem_ctx, + struct nss_domain_entry *e, + const char *name, + char **alias) +{ + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + const char *value; + + value = talloc_asprintf(mem_ctx, "%s\\%s", e->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, + struct nss_domain_entry *e, + 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 const struct idmap_methods hash_idmap_methods = { + .init = idmap_hash_initialize, + .unixids_to_sids = unixids_to_sids, + .sids_to_unixids = sids_to_unixids, +}; + +static const struct nss_info_methods hash_nss_methods = { + .init = nss_hash_init, + .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. + **********************************************************************/ + +static_decl_idmap; +NTSTATUS idmap_hash_init(TALLOC_CTX *ctx) +{ + 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 0000000..621520e --- /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 0000000..82812a1 --- /dev/null +++ b/source3/winbindd/idmap_hash/mapfile.c @@ -0,0 +1,182 @@ +/* + * 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 "system/filesys.h" +#include "winbindd/winbindd.h" +#include "idmap.h" +#include "idmap_hash.h" + +static FILE *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 (fseek(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 = fopen(mapfile_name, "r"); + 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; + + p = fgets(buffer, sizeof(buffer)-1, lw_map_file); + if (p == NULL) { + return false; + } + + /* Strip newlines and carriage returns */ + + len = strlen_m(buffer); + if (len == 0) { + return false; + } + len -= 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++; + + strlcpy(key, buffer, sizeof(fstring)); + strlcpy(value, p, sizeof(fstring)); + + /* 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 = 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_ldap.c b/source3/winbindd/idmap_ldap.c new file mode 100644 index 0000000..0b0d82b --- /dev/null +++ b/source3/winbindd/idmap_ldap.c @@ -0,0 +1,1140 @@ +/* + Unix SMB/CIFS implementation. + + idmap LDAP backend + + Copyright (C) Tim Potter 2000 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Gerald Carter 2003 + Copyright (C) Simo Sorce 2003-2007 + Copyright (C) Michael Adam 2010 + + 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.h" +#include "secrets.h" +#include "idmap.h" +#include "idmap_rw.h" +#include "../libcli/security/security.h" +#include "lib/util/smb_strtox.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +#include <lber.h> +#include <ldap.h> + +#include "smbldap.h" +#include "passdb/pdb_ldap_schema.h" + +struct idmap_ldap_context { + struct smbldap_state *smbldap_state; + char *url; + char *suffix; + char *user_dn; + bool anon; + struct idmap_rw_ops *rw_ops; +}; + +#define CHECK_ALLOC_DONE(mem) do { \ + if (!mem) { \ + DEBUG(0, ("Out of memory!\n")); \ + ret = NT_STATUS_NO_MEMORY; \ + goto done; \ + } } while (0) + +/********************************************************************** + IDMAP ALLOC TDB BACKEND +**********************************************************************/ + +/********************************************************************* + ********************************************************************/ + +static NTSTATUS get_credentials( TALLOC_CTX *mem_ctx, + struct smbldap_state *ldap_state, + struct idmap_domain *dom, + char **dn ) +{ + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + char *secret = NULL; + const char *tmp = NULL; + char *user_dn = NULL; + bool anon = False; + + /* assume anonymous if we don't have a specified user */ + + tmp = idmap_config_const_string(dom->name, "ldap_user_dn", NULL); + + if ( tmp ) { + secret = idmap_fetch_secret("ldap", dom->name, tmp); + if (!secret) { + DEBUG(0, ("get_credentials: Unable to fetch " + "auth credentials for %s in %s\n", + tmp, (dom==NULL)?"ALLOC":dom->name)); + ret = NT_STATUS_ACCESS_DENIED; + goto done; + } + *dn = talloc_strdup(mem_ctx, tmp); + CHECK_ALLOC_DONE(*dn); + } else { + if (!fetch_ldap_pw(&user_dn, &secret)) { + DEBUG(2, ("get_credentials: Failed to lookup ldap " + "bind creds. Using anonymous connection.\n")); + anon = True; + *dn = NULL; + } else { + *dn = talloc_strdup(mem_ctx, user_dn); + SAFE_FREE( user_dn ); + CHECK_ALLOC_DONE(*dn); + } + } + + smbldap_set_creds(ldap_state, anon, *dn, secret); + ret = NT_STATUS_OK; + +done: + BURN_FREE_STR(secret); + + return ret; +} + + +/********************************************************************** + Verify the sambaUnixIdPool entry in the directory. +**********************************************************************/ + +static NTSTATUS verify_idpool(struct idmap_domain *dom) +{ + NTSTATUS ret; + TALLOC_CTX *mem_ctx; + LDAPMessage *result = NULL; + LDAPMod **mods = NULL; + const char **attr_list; + char *filter; + int count; + int rc; + struct idmap_ldap_context *ctx; + + ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context); + + mem_ctx = talloc_new(ctx); + if (mem_ctx == NULL) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(mem_ctx, "(objectclass=%s)", LDAP_OBJ_IDPOOL); + CHECK_ALLOC_DONE(filter); + + attr_list = get_attr_list(mem_ctx, idpool_attr_list); + CHECK_ALLOC_DONE(attr_list); + + rc = smbldap_search(ctx->smbldap_state, + ctx->suffix, + LDAP_SCOPE_SUBTREE, + filter, + attr_list, + 0, + &result); + + if (rc != LDAP_SUCCESS) { + DEBUG(1, ("Unable to verify the idpool, " + "cannot continue initialization!\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + count = ldap_count_entries(smbldap_get_ldap(ctx->smbldap_state), + result); + + ldap_msgfree(result); + + if ( count > 1 ) { + DEBUG(0,("Multiple entries returned from %s (base == %s)\n", + filter, ctx->suffix)); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + else if (count == 0) { + char *uid_str, *gid_str; + + uid_str = talloc_asprintf(mem_ctx, "%lu", + (unsigned long)dom->low_id); + gid_str = talloc_asprintf(mem_ctx, "%lu", + (unsigned long)dom->low_id); + + smbldap_set_mod(&mods, LDAP_MOD_ADD, + "objectClass", LDAP_OBJ_IDPOOL); + smbldap_set_mod(&mods, LDAP_MOD_ADD, + get_attr_key2string(idpool_attr_list, + LDAP_ATTR_UIDNUMBER), + uid_str); + smbldap_set_mod(&mods, LDAP_MOD_ADD, + get_attr_key2string(idpool_attr_list, + LDAP_ATTR_GIDNUMBER), + gid_str); + if (mods) { + rc = smbldap_modify(ctx->smbldap_state, + ctx->suffix, + mods); + ldap_mods_free(mods, True); + } else { + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + ret = (rc == LDAP_SUCCESS)?NT_STATUS_OK:NT_STATUS_UNSUCCESSFUL; +done: + talloc_free(mem_ctx); + return ret; +} + +/******************************** + Allocate a new uid or gid +********************************/ + +static NTSTATUS idmap_ldap_allocate_id_internal(struct idmap_domain *dom, + struct unixid *xid) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS ret = NT_STATUS_UNSUCCESSFUL; + int rc = LDAP_SERVER_DOWN; + int count = 0; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + LDAPMod **mods = NULL; + char *id_str; + char *new_id_str; + char *filter = NULL; + const char *dn = NULL; + const char **attr_list; + const char *type; + struct idmap_ldap_context *ctx; + int error = 0; + + /* Only do query if we are online */ + if (idmap_is_offline()) { + return NT_STATUS_FILE_IS_OFFLINE; + } + + ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context); + + mem_ctx = talloc_new(ctx); + if (!mem_ctx) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + /* get type */ + switch (xid->type) { + + case ID_TYPE_UID: + type = get_attr_key2string(idpool_attr_list, + LDAP_ATTR_UIDNUMBER); + break; + + case ID_TYPE_GID: + type = get_attr_key2string(idpool_attr_list, + LDAP_ATTR_GIDNUMBER); + break; + + case ID_TYPE_BOTH: + /* + * This is not supported here yet and + * already handled in idmap_rw_new_mapping() + */ + FALL_THROUGH; + case ID_TYPE_NOT_SPECIFIED: + /* + * This is handled in idmap_rw_new_mapping() + */ + FALL_THROUGH; + default: + DEBUG(2, ("Invalid ID type (0x%x)\n", xid->type)); + return NT_STATUS_INVALID_PARAMETER; + } + + filter = talloc_asprintf(mem_ctx, "(objectClass=%s)", LDAP_OBJ_IDPOOL); + CHECK_ALLOC_DONE(filter); + + attr_list = get_attr_list(mem_ctx, idpool_attr_list); + CHECK_ALLOC_DONE(attr_list); + + DEBUG(10, ("Search of the id pool (filter: %s)\n", filter)); + + rc = smbldap_search(ctx->smbldap_state, + ctx->suffix, + LDAP_SCOPE_SUBTREE, filter, + attr_list, 0, &result); + + if (rc != LDAP_SUCCESS) { + DEBUG(0,("%s object not found\n", LDAP_OBJ_IDPOOL)); + goto done; + } + + smbldap_talloc_autofree_ldapmsg(mem_ctx, result); + + count = ldap_count_entries(smbldap_get_ldap(ctx->smbldap_state), + result); + if (count != 1) { + DEBUG(0,("Single %s object not found\n", LDAP_OBJ_IDPOOL)); + goto done; + } + + entry = ldap_first_entry(smbldap_get_ldap(ctx->smbldap_state), result); + + dn = smbldap_talloc_dn(mem_ctx, + smbldap_get_ldap(ctx->smbldap_state), + entry); + if ( ! dn) { + goto done; + } + + id_str = smbldap_talloc_single_attribute( + smbldap_get_ldap(ctx->smbldap_state), + entry, type, mem_ctx); + if (id_str == NULL) { + DEBUG(0,("%s attribute not found\n", type)); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + xid->id = smb_strtoul(id_str, NULL, 10, &error, SMB_STR_STANDARD); + if (error != 0) { + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + /* make sure we still have room to grow */ + + switch (xid->type) { + case ID_TYPE_UID: + if (xid->id > dom->high_id) { + DEBUG(0,("Cannot allocate uid above %lu!\n", + (unsigned long)dom->high_id)); + goto done; + } + break; + + case ID_TYPE_GID: + if (xid->id > dom->high_id) { + DEBUG(0,("Cannot allocate gid above %lu!\n", + (unsigned long)dom->high_id)); + goto done; + } + break; + + default: + /* impossible */ + goto done; + } + + new_id_str = talloc_asprintf(mem_ctx, "%lu", (unsigned long)xid->id + 1); + if ( ! new_id_str) { + DEBUG(0,("Out of memory\n")); + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + smbldap_set_mod(&mods, LDAP_MOD_DELETE, type, id_str); + smbldap_set_mod(&mods, LDAP_MOD_ADD, type, new_id_str); + + if (mods == NULL) { + DEBUG(0,("smbldap_set_mod() failed.\n")); + goto done; + } + + DEBUG(10, ("Try to atomically increment the id (%s -> %s)\n", + id_str, new_id_str)); + + rc = smbldap_modify(ctx->smbldap_state, dn, mods); + + ldap_mods_free(mods, True); + + if (rc != LDAP_SUCCESS) { + DEBUG(1,("Failed to allocate new %s. " + "smbldap_modify() failed.\n", type)); + goto done; + } + + ret = NT_STATUS_OK; + +done: + talloc_free(mem_ctx); + return ret; +} + +/** + * Allocate a new unix-ID. + * For now this is for the default idmap domain only. + * Should be extended later on. + */ +static NTSTATUS idmap_ldap_allocate_id(struct idmap_domain *dom, + struct unixid *id) +{ + NTSTATUS ret; + + if (!strequal(dom->name, "*")) { + DEBUG(3, ("idmap_ldap_allocate_id: " + "Refusing allocation of a new unixid for domain'%s'. " + "This is only supported for the default " + "domain \"*\".\n", + dom->name)); + return NT_STATUS_NOT_IMPLEMENTED; + } + + ret = idmap_ldap_allocate_id_internal(dom, id); + + return ret; +} + + +/********************************************************************** + IDMAP MAPPING LDAP BACKEND +**********************************************************************/ + +static int idmap_ldap_close_destructor(struct idmap_ldap_context *ctx) +{ + smbldap_free_struct(&ctx->smbldap_state); + DEBUG(5,("The connection to the LDAP server was closed\n")); + /* maybe free the results here --metze */ + + return 0; +} + +/******************************** + Initialise idmap database. +********************************/ + +static NTSTATUS idmap_ldap_set_mapping(struct idmap_domain *dom, + const struct id_map *map); + +static NTSTATUS idmap_ldap_db_init(struct idmap_domain *dom) +{ + NTSTATUS ret; + struct idmap_ldap_context *ctx = NULL; + const char *tmp = NULL; + + /* Only do init if we are online */ + if (idmap_is_offline()) { + return NT_STATUS_FILE_IS_OFFLINE; + } + + ctx = talloc_zero(dom, struct idmap_ldap_context); + if ( ! ctx) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + tmp = idmap_config_const_string(dom->name, "ldap_url", NULL); + + if ( ! tmp) { + DEBUG(1, ("ERROR: missing idmap ldap url\n")); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + ctx->url = talloc_strdup(ctx, tmp); + + trim_char(ctx->url, '\"', '\"'); + + tmp = idmap_config_const_string(dom->name, "ldap_base_dn", NULL); + if ( ! tmp || ! *tmp) { + tmp = lp_ldap_idmap_suffix(talloc_tos()); + if ( ! tmp) { + DEBUG(1, ("ERROR: missing idmap ldap suffix\n")); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + } + + ctx->suffix = talloc_strdup(ctx, tmp); + CHECK_ALLOC_DONE(ctx->suffix); + + ctx->rw_ops = talloc_zero(ctx, struct idmap_rw_ops); + CHECK_ALLOC_DONE(ctx->rw_ops); + + ctx->rw_ops->get_new_id = idmap_ldap_allocate_id_internal; + ctx->rw_ops->set_mapping = idmap_ldap_set_mapping; + + /* get_credentials deals with setting up creds */ + + ret = smbldap_init(ctx, global_event_context(), ctx->url, + false, NULL, NULL, &ctx->smbldap_state); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("ERROR: smbldap_init (%s) failed!\n", ctx->url)); + goto done; + } + + ret = get_credentials( ctx, ctx->smbldap_state, + dom, &ctx->user_dn ); + if ( !NT_STATUS_IS_OK(ret) ) { + DEBUG(1,("idmap_ldap_db_init: Failed to get connection " + "credentials (%s)\n", nt_errstr(ret))); + goto done; + } + + /* + * Set the destructor on the context, so that resources are + * properly freed when the context is released. + */ + talloc_set_destructor(ctx, idmap_ldap_close_destructor); + + dom->private_data = ctx; + + ret = verify_idpool(dom); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("idmap_ldap_db_init: failed to verify ID pool (%s)\n", + nt_errstr(ret))); + goto done; + } + + return NT_STATUS_OK; + +/*failed */ +done: + talloc_free(ctx); + return ret; +} + +/** + * set a mapping. + */ + +/* TODO: change this: This function cannot be called to modify a mapping, + * only set a new one */ + +static NTSTATUS idmap_ldap_set_mapping(struct idmap_domain *dom, + const struct id_map *map) +{ + NTSTATUS ret; + TALLOC_CTX *memctx; + struct idmap_ldap_context *ctx; + LDAPMessage *entry = NULL; + LDAPMod **mods = NULL; + const char *type; + char *id_str; + struct dom_sid_buf sid; + char *dn; + int rc = -1; + + /* Only do query if we are online */ + if (idmap_is_offline()) { + return NT_STATUS_FILE_IS_OFFLINE; + } + + ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context); + + switch(map->xid.type) { + case ID_TYPE_UID: + type = get_attr_key2string(sidmap_attr_list, + LDAP_ATTR_UIDNUMBER); + break; + + case ID_TYPE_GID: + type = get_attr_key2string(sidmap_attr_list, + LDAP_ATTR_GIDNUMBER); + break; + + default: + return NT_STATUS_INVALID_PARAMETER; + } + + memctx = talloc_new(ctx); + if ( ! memctx) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + id_str = talloc_asprintf(memctx, "%lu", (unsigned long)map->xid.id); + CHECK_ALLOC_DONE(id_str); + + dn = talloc_asprintf(memctx, "%s=%s,%s", + get_attr_key2string(sidmap_attr_list, LDAP_ATTR_SID), + dom_sid_str_buf(map->sid, &sid), + ctx->suffix); + CHECK_ALLOC_DONE(dn); + + smbldap_set_mod(&mods, LDAP_MOD_ADD, + "objectClass", LDAP_OBJ_IDMAP_ENTRY); + + smbldap_make_mod(smbldap_get_ldap(ctx->smbldap_state), + entry, &mods, type, id_str); + + smbldap_make_mod(smbldap_get_ldap(ctx->smbldap_state), entry, &mods, + get_attr_key2string(sidmap_attr_list, LDAP_ATTR_SID), + sid.buf); + + if ( ! mods) { + DEBUG(2, ("ERROR: No mods?\n")); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + /* TODO: remove conflicting mappings! */ + + smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_SID_ENTRY); + + DEBUG(10, ("Set DN %s (%s -> %s)\n", dn, sid.buf, id_str)); + + rc = smbldap_add(ctx->smbldap_state, dn, mods); + ldap_mods_free(mods, True); + + if (rc != LDAP_SUCCESS) { + char *ld_error = NULL; + ldap_get_option(smbldap_get_ldap(ctx->smbldap_state), + LDAP_OPT_ERROR_STRING, &ld_error); + DEBUG(0,("ldap_set_mapping_internals: Failed to add %s to %lu " + "mapping [%s]\n", sid.buf, + (unsigned long)map->xid.id, type)); + DEBUG(0, ("ldap_set_mapping_internals: Error was: %s (%s)\n", + ld_error ? ld_error : "(NULL)", ldap_err2string (rc))); + if (ld_error) { + ldap_memfree(ld_error); + } + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + DEBUG(10,("ldap_set_mapping: Successfully created mapping from %s to " + "%lu [%s]\n", sid.buf, (unsigned long)map->xid.id, type)); + + ret = NT_STATUS_OK; + +done: + talloc_free(memctx); + return ret; +} + +/** + * Create a new mapping for an unmapped SID, also allocating a new ID. + * If possible, this should be run inside a transaction to make the + * action atomic. + */ +static NTSTATUS idmap_ldap_new_mapping(struct idmap_domain *dom, struct id_map *map) +{ + NTSTATUS ret; + struct idmap_ldap_context *ctx; + + ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context); + + ret = idmap_rw_new_mapping(dom, ctx->rw_ops, map); + + return ret; +} + +/********************************** + lookup a set of unix ids. +**********************************/ + +static NTSTATUS idmap_ldap_unixids_to_sids(struct idmap_domain *dom, + struct id_map **ids) +{ + NTSTATUS ret; + TALLOC_CTX *memctx; + struct idmap_ldap_context *ctx; + LDAPMessage *result = NULL; + LDAPMessage *entry = NULL; + const char *uidNumber; + const char *gidNumber; + const char **attr_list; + char *filter = NULL; + bool multi = False; + int idx = 0; + int bidx = 0; + int count; + int rc; + int i; + int error = 0; + + /* Only do query if we are online */ + if (idmap_is_offline()) { + return NT_STATUS_FILE_IS_OFFLINE; + } + + ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context); + + memctx = talloc_new(ctx); + if ( ! memctx) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + uidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_UIDNUMBER); + gidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_GIDNUMBER); + + attr_list = get_attr_list(memctx, sidmap_attr_list); + + if ( ! ids[1]) { + /* if we are requested just one mapping use the simple filter */ + + filter = talloc_asprintf(memctx, "(&(objectClass=%s)(%s=%lu))", + LDAP_OBJ_IDMAP_ENTRY, + (ids[0]->xid.type==ID_TYPE_UID)?uidNumber:gidNumber, + (unsigned long)ids[0]->xid.id); + CHECK_ALLOC_DONE(filter); + DEBUG(10, ("Filter: [%s]\n", filter)); + } else { + /* multiple mappings */ + multi = True; + } + + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + +again: + if (multi) { + + talloc_free(filter); + filter = talloc_asprintf(memctx, + "(&(objectClass=%s)(|", + LDAP_OBJ_IDMAP_ENTRY); + CHECK_ALLOC_DONE(filter); + + bidx = idx; + for (i = 0; (i < IDMAP_LDAP_MAX_IDS) && ids[idx]; i++, idx++) { + filter = talloc_asprintf_append_buffer(filter, "(%s=%lu)", + (ids[idx]->xid.type==ID_TYPE_UID)?uidNumber:gidNumber, + (unsigned long)ids[idx]->xid.id); + CHECK_ALLOC_DONE(filter); + } + filter = talloc_asprintf_append_buffer(filter, "))"); + CHECK_ALLOC_DONE(filter); + DEBUG(10, ("Filter: [%s]\n", filter)); + } else { + bidx = 0; + idx = 1; + } + + rc = smbldap_search(ctx->smbldap_state, ctx->suffix, LDAP_SCOPE_SUBTREE, + filter, attr_list, 0, &result); + + if (rc != LDAP_SUCCESS) { + DEBUG(3,("Failure looking up ids (%s)\n", ldap_err2string(rc))); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + count = ldap_count_entries(smbldap_get_ldap(ctx->smbldap_state), + result); + + if (count == 0) { + DEBUG(10, ("NO SIDs found\n")); + } + + for (i = 0; i < count; i++) { + char *sidstr = NULL; + char *tmp = NULL; + enum id_type type; + struct id_map *map; + uint32_t id; + struct dom_sid_buf buf; + + if (i == 0) { /* first entry */ + entry = ldap_first_entry( + smbldap_get_ldap(ctx->smbldap_state), result); + } else { /* following ones */ + entry = ldap_next_entry( + smbldap_get_ldap(ctx->smbldap_state), entry); + } + if ( ! entry) { + DEBUG(2, ("ERROR: Unable to fetch ldap entries " + "from results\n")); + break; + } + + /* first check if the SID is present */ + sidstr = smbldap_talloc_single_attribute( + smbldap_get_ldap(ctx->smbldap_state), + entry, LDAP_ATTRIBUTE_SID, memctx); + if ( ! sidstr) { /* no sid, skip entry */ + DEBUG(2, ("WARNING SID not found on entry\n")); + continue; + } + + /* now try to see if it is a uid, if not try with a gid + * (gid is more common, but in case both uidNumber and + * gidNumber are returned the SID is mapped to the uid + *not the gid) */ + type = ID_TYPE_UID; + tmp = smbldap_talloc_single_attribute( + smbldap_get_ldap(ctx->smbldap_state), + entry, uidNumber, memctx); + if ( ! tmp) { + type = ID_TYPE_GID; + tmp = smbldap_talloc_single_attribute( + smbldap_get_ldap(ctx->smbldap_state), + entry, gidNumber, memctx); + } + if ( ! tmp) { /* wow very strange entry, how did it match ? */ + DEBUG(5, ("Improbable match on (%s), no uidNumber, " + "nor gidNumber returned\n", sidstr)); + TALLOC_FREE(sidstr); + continue; + } + + id = smb_strtoul(tmp, NULL, 10, &error, SMB_STR_STANDARD); + TALLOC_FREE(tmp); + if (error != 0) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u). " + "Filtered!\n", id, + dom->low_id, dom->high_id)); + TALLOC_FREE(sidstr); + continue; + } + + if (!idmap_unix_id_is_in_range(id, dom)) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u). " + "Filtered!\n", id, + dom->low_id, dom->high_id)); + TALLOC_FREE(sidstr); + continue; + } + + map = idmap_find_map_by_id(&ids[bidx], type, id); + if (!map) { + DEBUG(2, ("WARNING: couldn't match sid (%s) " + "with requested ids\n", sidstr)); + TALLOC_FREE(sidstr); + continue; + } + + if ( ! string_to_sid(map->sid, sidstr)) { + DEBUG(2, ("ERROR: Invalid SID on entry\n")); + TALLOC_FREE(sidstr); + continue; + } + + if (map->status == ID_MAPPED) { + DEBUG(1, ("WARNING: duplicate %s mapping in LDAP. " + "overwriting mapping %u -> %s with %u -> %s\n", + (type == ID_TYPE_UID) ? "UID" : "GID", + id, + dom_sid_str_buf(map->sid, &buf), + id, + sidstr)); + } + + TALLOC_FREE(sidstr); + + /* mapped */ + map->status = ID_MAPPED; + + DEBUG(10, ("Mapped %s -> %lu (%d)\n", + dom_sid_str_buf(map->sid, &buf), + (unsigned long)map->xid.id, map->xid.type)); + } + + /* free the ldap results */ + if (result) { + ldap_msgfree(result); + result = NULL; + } + + if (multi && ids[idx]) { /* still some values to map */ + goto again; + } + + ret = NT_STATUS_OK; + + /* mark all unknown/expired ones as unmapped */ + for (i = 0; ids[i]; i++) { + if (ids[i]->status != ID_MAPPED) + ids[i]->status = ID_UNMAPPED; + } + +done: + talloc_free(memctx); + return ret; +} + +/********************************** + lookup a set of sids. +**********************************/ + +static NTSTATUS idmap_ldap_sids_to_unixids(struct idmap_domain *dom, + struct id_map **ids) +{ + LDAPMessage *entry = NULL; + NTSTATUS ret; + TALLOC_CTX *memctx; + struct idmap_ldap_context *ctx; + LDAPMessage *result = NULL; + const char *uidNumber; + const char *gidNumber; + const char **attr_list; + char *filter = NULL; + bool multi = False; + size_t num_required = 0; + int idx = 0; + int bidx = 0; + int count; + int rc; + int i; + + /* Only do query if we are online */ + if (idmap_is_offline()) { + return NT_STATUS_FILE_IS_OFFLINE; + } + + ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context); + + memctx = talloc_new(ctx); + if ( ! memctx) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + uidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_UIDNUMBER); + gidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_GIDNUMBER); + + attr_list = get_attr_list(memctx, sidmap_attr_list); + + if ( ! ids[1]) { + struct dom_sid_buf buf; + /* if we are requested just one mapping use the simple filter */ + + filter = talloc_asprintf(memctx, "(&(objectClass=%s)(%s=%s))", + LDAP_OBJ_IDMAP_ENTRY, + LDAP_ATTRIBUTE_SID, + dom_sid_str_buf(ids[0]->sid, &buf)); + CHECK_ALLOC_DONE(filter); + DEBUG(10, ("Filter: [%s]\n", filter)); + } else { + /* multiple mappings */ + multi = True; + } + + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + +again: + if (multi) { + + TALLOC_FREE(filter); + filter = talloc_asprintf(memctx, + "(&(objectClass=%s)(|", + LDAP_OBJ_IDMAP_ENTRY); + CHECK_ALLOC_DONE(filter); + + bidx = idx; + for (i = 0; (i < IDMAP_LDAP_MAX_IDS) && ids[idx]; i++, idx++) { + struct dom_sid_buf buf; + filter = talloc_asprintf_append_buffer(filter, "("LDAP_ATTRIBUTE_SID"=%s)", + dom_sid_str_buf(ids[idx]->sid, &buf)); + CHECK_ALLOC_DONE(filter); + } + filter = talloc_asprintf_append_buffer(filter, "))"); + CHECK_ALLOC_DONE(filter); + DEBUG(10, ("Filter: [%s]\n", filter)); + } else { + bidx = 0; + idx = 1; + } + + rc = smbldap_search(ctx->smbldap_state, ctx->suffix, LDAP_SCOPE_SUBTREE, + filter, attr_list, 0, &result); + + if (rc != LDAP_SUCCESS) { + DEBUG(3,("Failure looking up sids (%s)\n", + ldap_err2string(rc))); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + count = ldap_count_entries(smbldap_get_ldap(ctx->smbldap_state), + result); + + if (count == 0) { + DEBUG(10, ("NO SIDs found\n")); + } + + for (i = 0; i < count; i++) { + char *sidstr = NULL; + char *tmp = NULL; + enum id_type type; + struct id_map *map; + struct dom_sid sid; + struct dom_sid_buf buf; + uint32_t id; + int error = 0; + + if (i == 0) { /* first entry */ + entry = ldap_first_entry( + smbldap_get_ldap(ctx->smbldap_state), result); + } else { /* following ones */ + entry = ldap_next_entry( + smbldap_get_ldap(ctx->smbldap_state), entry); + } + if ( ! entry) { + DEBUG(2, ("ERROR: Unable to fetch ldap entries " + "from results\n")); + break; + } + + /* first check if the SID is present */ + sidstr = smbldap_talloc_single_attribute( + smbldap_get_ldap(ctx->smbldap_state), + entry, LDAP_ATTRIBUTE_SID, memctx); + if ( ! sidstr) { /* no sid ??, skip entry */ + DEBUG(2, ("WARNING SID not found on entry\n")); + continue; + } + + if ( ! string_to_sid(&sid, sidstr)) { + DEBUG(2, ("ERROR: Invalid SID on entry\n")); + TALLOC_FREE(sidstr); + continue; + } + + map = idmap_find_map_by_sid(&ids[bidx], &sid); + if (!map) { + DEBUG(2, ("WARNING: couldn't find entry sid (%s) " + "in ids\n", sidstr)); + TALLOC_FREE(sidstr); + continue; + } + + /* now try to see if it is a uid, if not try with a gid + * (gid is more common, but in case both uidNumber and + * gidNumber are returned the SID is mapped to the uid + * not the gid) */ + type = ID_TYPE_UID; + tmp = smbldap_talloc_single_attribute( + smbldap_get_ldap(ctx->smbldap_state), + entry, uidNumber, memctx); + if ( ! tmp) { + type = ID_TYPE_GID; + tmp = smbldap_talloc_single_attribute( + smbldap_get_ldap(ctx->smbldap_state), + entry, gidNumber, memctx); + } + if ( ! tmp) { /* no ids ?? */ + DEBUG(5, ("no uidNumber, " + "nor gidNumber attributes found\n")); + TALLOC_FREE(sidstr); + continue; + } + + id = smb_strtoul(tmp, NULL, 10, &error, SMB_STR_STANDARD); + TALLOC_FREE(tmp); + if (error != 0) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u). " + "Filtered!\n", id, + dom->low_id, dom->high_id)); + TALLOC_FREE(sidstr); + continue; + } + + if (error != 0 || !idmap_unix_id_is_in_range(id, dom)) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u). " + "Filtered!\n", id, + dom->low_id, dom->high_id)); + TALLOC_FREE(sidstr); + continue; + } + + if (map->status == ID_MAPPED) { + DEBUG(1, ("WARNING: duplicate %s mapping in LDAP. " + "overwriting mapping %s -> %u with %s -> %u\n", + (type == ID_TYPE_UID) ? "UID" : "GID", + sidstr, map->xid.id, sidstr, id)); + } + + TALLOC_FREE(sidstr); + + /* mapped */ + map->xid.type = type; + map->xid.id = id; + map->status = ID_MAPPED; + + DEBUG(10, ("Mapped %s -> %lu (%d)\n", + dom_sid_str_buf(map->sid, &buf), + (unsigned long)map->xid.id, + map->xid.type)); + } + + /* free the ldap results */ + if (result) { + ldap_msgfree(result); + result = NULL; + } + + if (multi && ids[idx]) { /* still some values to map */ + goto again; + } + + /* + * try to create new mappings for unmapped sids + */ + for (i = 0; ids[i]; i++) { + if (ids[i]->status != ID_MAPPED) { + ids[i]->status = ID_UNMAPPED; + if (ids[i]->sid != NULL) { + ret = idmap_ldap_new_mapping(dom, ids[i]); + DBG_DEBUG("idmap_ldap_new_mapping returned %s\n", + nt_errstr(ret)); + if (NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED)) { + if (ids[i]->status == ID_REQUIRE_TYPE) { + num_required += 1; + continue; + } + } + if (!NT_STATUS_IS_OK(ret)) { + /* + * If we can't create + * a new mapping it's unlikely + * that it will work for the + * next entry. + */ + goto done; + } + } + } + } + + ret = NT_STATUS_OK; + if (num_required > 0) { + ret = STATUS_SOME_UNMAPPED; + } + +done: + talloc_free(memctx); + return ret; +} + +/********************************** + Close the idmap ldap instance +**********************************/ + +static const struct idmap_methods idmap_ldap_methods = { + + .init = idmap_ldap_db_init, + .unixids_to_sids = idmap_ldap_unixids_to_sids, + .sids_to_unixids = idmap_ldap_sids_to_unixids, + .allocate_id = idmap_ldap_allocate_id, +}; + +NTSTATUS idmap_ldap_init(TALLOC_CTX *); +NTSTATUS idmap_ldap_init(TALLOC_CTX *ctx) +{ + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "ldap", + &idmap_ldap_methods); +} + diff --git a/source3/winbindd/idmap_nss.c b/source3/winbindd/idmap_nss.c new file mode 100644 index 0000000..0af2536 --- /dev/null +++ b/source3/winbindd/idmap_nss.c @@ -0,0 +1,446 @@ +/* + Unix SMB/CIFS implementation. + + idmap NSS backend + + Copyright (C) Simo Sorce 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/passwd.h" +#include "winbindd.h" +#include "nsswitch/winbind_client.h" +#include "idmap.h" +#include "lib/winbind_util.h" +#include "libcli/security/dom_sid.h" +#include "lib/global_contexts.h" +#include "messages.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +struct idmap_nss_context { + struct idmap_domain *dom; + bool use_upn; +}; + +static int idmap_nss_context_destructor(struct idmap_nss_context *ctx) +{ + if ((ctx->dom != NULL) && (ctx->dom->private_data == ctx)) { + ctx->dom->private_data = NULL; + } + return 0; +} + +static NTSTATUS idmap_nss_context_create(TALLOC_CTX *mem_ctx, + struct idmap_domain *dom, + struct idmap_nss_context **pctx) +{ + struct idmap_nss_context *ctx = NULL; + + ctx = talloc_zero(mem_ctx, struct idmap_nss_context); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + ctx->dom = dom; + + talloc_set_destructor(ctx, idmap_nss_context_destructor); + + ctx->use_upn = idmap_config_bool(dom->name, "use_upn", false); + + *pctx = ctx; + return NT_STATUS_OK; +} + +static NTSTATUS idmap_nss_get_context(struct idmap_domain *dom, + struct idmap_nss_context **pctx) +{ + struct idmap_nss_context *ctx = NULL; + NTSTATUS status; + + if (dom->private_data != NULL) { + *pctx = talloc_get_type_abort(dom->private_data, + struct idmap_nss_context); + return NT_STATUS_OK; + } + + status = idmap_nss_context_create(dom, dom, &ctx); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("idmap_nss_context_create failed: %s\n", + nt_errstr(status)); + return status; + } + + dom->private_data = ctx; + *pctx = ctx; + return NT_STATUS_OK; +} + +static bool idmap_nss_msg_filter(struct messaging_rec *rec, void *private_data) +{ + struct idmap_domain *dom = talloc_get_type_abort(private_data, + struct idmap_domain); + struct idmap_nss_context *ctx = NULL; + NTSTATUS status; + bool ret; + + if (rec->msg_type == MSG_SMB_CONF_UPDATED) { + ret = lp_load_global(get_dyn_CONFIGFILE()); + if (!ret) { + DBG_WARNING("Failed to reload configuration\n"); + return false; + } + + status = idmap_nss_get_context(dom, &ctx); + if (NT_STATUS_IS_ERR(status)) { + DBG_WARNING("Failed to get idmap nss context: %s\n", + nt_errstr(status)); + return false; + } + + ctx->use_upn = idmap_config_bool(dom->name, "use_upn", false); + } + + return false; +} + +/***************************** + Initialise idmap database. +*****************************/ + +static NTSTATUS idmap_nss_int_init(struct idmap_domain *dom) +{ + struct idmap_nss_context *ctx = NULL; + NTSTATUS status; + struct messaging_context *msg_ctx = global_messaging_context(); + struct tevent_req *req = NULL; + + status = idmap_nss_context_create(dom, dom, &ctx); + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + dom->private_data = ctx; + + req = messaging_filtered_read_send( + dom, + messaging_tevent_context(msg_ctx), + msg_ctx, + idmap_nss_msg_filter, + dom); + if (req == NULL) { + DBG_WARNING("messaging_filtered_read_send failed\n"); + return NT_STATUS_UNSUCCESSFUL; + } + + return status; +} + +static NTSTATUS idmap_nss_lookup_name(const char *namespace, + const char *username, + struct dom_sid *sid, + enum lsa_SidType *type) +{ + bool ret; + + /* + * By default calls to winbindd are disabled + * the following call will not recurse so this is safe + */ + (void)winbind_on(); + ret = winbind_lookup_name(namespace, username, sid, type); + (void)winbind_off(); + + if (!ret) { + DBG_NOTICE("Failed to lookup name [%s] in namespace [%s]\n", + username, namespace); + return NT_STATUS_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/********************************** + lookup a set of unix ids. +**********************************/ + +static NTSTATUS idmap_nss_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids) +{ + struct idmap_nss_context *ctx = NULL; + NTSTATUS status; + int i; + + status = idmap_nss_get_context(dom, &ctx); + if (NT_STATUS_IS_ERR(status)) { + DBG_WARNING("Failed to get idmap nss context: %s\n", + nt_errstr(status)); + return status; + } + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + + for (i = 0; ids[i]; i++) { + struct passwd *pw; + struct group *gr; + const char *name; + struct dom_sid sid; + enum lsa_SidType type; + + switch (ids[i]->xid.type) { + case ID_TYPE_UID: + errno = 0; + pw = getpwuid((uid_t)ids[i]->xid.id); + if (!pw) { + DBG_DEBUG("getpwuid(%lu) failed: %s\n", + (unsigned long)ids[i]->xid.id, + errno != 0 + ? strerror(errno) + : "not found"); + ids[i]->status = ID_UNMAPPED; + continue; + } + name = pw->pw_name; + break; + case ID_TYPE_GID: + errno = 0; + gr = getgrgid((gid_t)ids[i]->xid.id); + if (!gr) { + DBG_DEBUG("getgrgid(%lu) failed: %s\n", + (unsigned long)ids[i]->xid.id, + errno != 0 + ? strerror(errno) + : "not found"); + ids[i]->status = ID_UNMAPPED; + continue; + } + name = gr->gr_name; + break; + default: /* ?? */ + DBG_WARNING("Unexpected xid type %d\n", + ids[i]->xid.type); + ids[i]->status = ID_UNKNOWN; + continue; + } + + /* Lookup name from PDC using lsa_lookup_names() */ + if (ctx->use_upn) { + char *p = NULL; + const char *namespace = NULL; + const char *domname = NULL; + const char *domuser = NULL; + + p = strstr(name, lp_winbind_separator()); + if (p != NULL) { + *p = '\0'; + domname = name; + namespace = domname; + domuser = p + 1; + } else { + p = strchr(name, '@'); + if (p != NULL) { + *p = '\0'; + namespace = p + 1; + domname = ""; + domuser = name; + } else { + namespace = dom->name; + domuser = name; + } + } + + DBG_DEBUG("Using namespace [%s] from UPN instead " + "of [%s] to lookup the name [%s]\n", + namespace, dom->name, domuser); + + status = idmap_nss_lookup_name(namespace, + domuser, + &sid, + &type); + } else { + status = idmap_nss_lookup_name(dom->name, + name, + &sid, + &type); + } + + if (NT_STATUS_IS_ERR(status)) { + /* + * TODO: how do we know if the name is really + * not mapped, or something just failed ? + */ + ids[i]->status = ID_UNMAPPED; + continue; + } + + switch (type) { + case SID_NAME_USER: + if (ids[i]->xid.type == ID_TYPE_UID) { + sid_copy(ids[i]->sid, &sid); + ids[i]->status = ID_MAPPED; + } + break; + + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + if (ids[i]->xid.type == ID_TYPE_GID) { + sid_copy(ids[i]->sid, &sid); + ids[i]->status = ID_MAPPED; + } + break; + + default: + ids[i]->status = ID_UNKNOWN; + break; + } + } + return NT_STATUS_OK; +} + +/********************************** + lookup a set of sids. +**********************************/ + +static NTSTATUS idmap_nss_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids) +{ + struct idmap_nss_context *ctx = NULL; + NTSTATUS status; + int i; + + status = idmap_nss_get_context(dom, &ctx); + if (NT_STATUS_IS_ERR(status)) { + DBG_WARNING("Failed to get idmap nss context: %s\n", + nt_errstr(status)); + return status; + } + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + + for (i = 0; ids[i]; i++) { + struct group *gr; + enum lsa_SidType type; + const char *_domain = NULL; + const char *_name = NULL; + char *domain = NULL; + char *name = NULL; + char *fqdn = NULL; + char *sname = NULL; + bool ret; + + /* by default calls to winbindd are disabled + the following call will not recurse so this is safe */ + (void)winbind_on(); + ret = winbind_lookup_sid(talloc_tos(), + ids[i]->sid, + &_domain, + &_name, + &type); + (void)winbind_off(); + if (!ret) { + /* TODO: how do we know if the name is really not mapped, + * or something just failed ? */ + ids[i]->status = ID_UNMAPPED; + continue; + } + + domain = discard_const_p(char, _domain); + name = discard_const_p(char, _name); + + if (!strequal(domain, dom->name)) { + struct dom_sid_buf buf; + DBG_ERR("DOMAIN[%s] ignoring SID[%s] belongs to %s [%s\\%s]\n", + dom->name, dom_sid_str_buf(ids[i]->sid, &buf), + sid_type_lookup(type), domain, name); + ids[i]->status = ID_UNMAPPED; + continue; + } + + if (ctx->use_upn) { + fqdn = talloc_asprintf(talloc_tos(), + "%s%s%s", + domain, + lp_winbind_separator(), + name); + if (fqdn == NULL) { + DBG_ERR("No memory\n"); + ids[i]->status = ID_UNMAPPED; + continue; + } + DBG_DEBUG("Using UPN [%s] instead of plain name [%s]\n", + fqdn, name); + sname = fqdn; + } else { + sname = name; + } + + switch (type) { + case SID_NAME_USER: { + struct passwd *pw; + + /* this will find also all lower case name and use username level */ + pw = Get_Pwnam_alloc(talloc_tos(), sname); + if (pw) { + ids[i]->xid.id = pw->pw_uid; + ids[i]->xid.type = ID_TYPE_UID; + ids[i]->status = ID_MAPPED; + } + TALLOC_FREE(pw); + break; + } + + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + + gr = getgrnam(sname); + if (gr) { + ids[i]->xid.id = gr->gr_gid; + ids[i]->xid.type = ID_TYPE_GID; + ids[i]->status = ID_MAPPED; + } + break; + + default: + ids[i]->status = ID_UNKNOWN; + break; + } + TALLOC_FREE(domain); + TALLOC_FREE(name); + TALLOC_FREE(fqdn); + } + return NT_STATUS_OK; +} + +/********************************** + Close the idmap tdb instance +**********************************/ + +static const struct idmap_methods nss_methods = { + .init = idmap_nss_int_init, + .unixids_to_sids = idmap_nss_unixids_to_sids, + .sids_to_unixids = idmap_nss_sids_to_unixids, +}; + +NTSTATUS idmap_nss_init(TALLOC_CTX *mem_ctx) +{ + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "nss", &nss_methods); +} diff --git a/source3/winbindd/idmap_passdb.c b/source3/winbindd/idmap_passdb.c new file mode 100644 index 0000000..ab02119 --- /dev/null +++ b/source3/winbindd/idmap_passdb.c @@ -0,0 +1,87 @@ +/* + Unix SMB/CIFS implementation. + + idmap PASSDB backend + + Copyright (C) Simo Sorce 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "idmap.h" +#include "passdb.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +/***************************** + Initialise idmap database. +*****************************/ + +static NTSTATUS idmap_pdb_init(struct idmap_domain *dom) +{ + return NT_STATUS_OK; +} + +/********************************** + lookup a set of unix ids. +**********************************/ + +static NTSTATUS idmap_pdb_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids) +{ + int i; + + for (i = 0; ids[i]; i++) { + /* unmapped by default */ + ids[i]->status = ID_UNMAPPED; + + if (pdb_id_to_sid(&ids[i]->xid, ids[i]->sid)) { + ids[i]->status = ID_MAPPED; + } + } + + return NT_STATUS_OK; +} + +/********************************** + lookup a set of sids. +**********************************/ + +static NTSTATUS idmap_pdb_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids) +{ + int i; + + for (i = 0; ids[i]; i++) { + if (pdb_sid_to_id(ids[i]->sid, &ids[i]->xid)) { + ids[i]->status = ID_MAPPED; + } else { + /* Query Failed */ + ids[i]->status = ID_UNMAPPED; + } + } + + return NT_STATUS_OK; +} + +static const struct idmap_methods passdb_methods = { + .init = idmap_pdb_init, + .unixids_to_sids = idmap_pdb_unixids_to_sids, + .sids_to_unixids = idmap_pdb_sids_to_unixids, +}; + +NTSTATUS idmap_passdb_init(TALLOC_CTX *mem_ctx) +{ + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "passdb", &passdb_methods); +} diff --git a/source3/winbindd/idmap_proto.h b/source3/winbindd/idmap_proto.h new file mode 100644 index 0000000..adc0443 --- /dev/null +++ b/source3/winbindd/idmap_proto.h @@ -0,0 +1,69 @@ +/* + * Unix SMB/CIFS implementation. + * ID Mapping + * + * Copyright (C) Tim Potter 2000 + * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + * Copyright (C) Simo Sorce 2003-2007 + * Copyright (C) Jeremy Allison 2006 + * Copyright (C) Michael Adam 2009-2010 + * + * 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 _WINBINDD_IDMAP_PROTO_H_ +#define _WINBINDD_IDMAP_PROTO_H_ + +/* The following definitions come from winbindd/idmap.c */ + +bool idmap_is_offline(void); +NTSTATUS smb_register_idmap(int version, const char *name, + const struct idmap_methods *methods); +void idmap_close(void); +NTSTATUS idmap_allocate_uid(struct unixid *id); +NTSTATUS idmap_allocate_gid(struct unixid *id); +NTSTATUS idmap_backend_unixids_to_sids(struct id_map **maps, + const char *domain_name, + struct dom_sid domain_sid); +struct idmap_domain *idmap_find_domain(const char *domname); + +/* The following definitions come from winbindd/idmap_nss.c */ + +NTSTATUS idmap_nss_init(TALLOC_CTX *mem_ctx); + +/* The following definitions come from winbindd/idmap_passdb.c */ + +NTSTATUS idmap_passdb_init(TALLOC_CTX *mem_ctx); + +/* The following definitions come from winbindd/idmap_tdb.c */ + +NTSTATUS idmap_tdb_init(TALLOC_CTX *mem_ctx); + +/* The following definitions come from winbindd/idmap_util.c */ + +bool idmap_unix_id_is_in_range(uint32_t id, struct idmap_domain *dom); +struct id_map *idmap_find_map_by_id(struct id_map **maps, enum id_type type, + uint32_t id); +struct id_map *idmap_find_map_by_sid(struct id_map **maps, struct dom_sid *sid); +char *idmap_fetch_secret(const char *backend, const char *domain, + const char *identity); + +struct id_map **id_map_ptrs_init(TALLOC_CTX *mem_ctx, size_t num_ids); + +/* max number of ids requested per LDAP batch query */ +#define IDMAP_LDAP_MAX_IDS 30 + +NTSTATUS idmap_ad_nss_init(TALLOC_CTX *mem_ctx); + +#endif /* _WINBINDD_IDMAP_PROTO_H_ */ diff --git a/source3/winbindd/idmap_rfc2307.c b/source3/winbindd/idmap_rfc2307.c new file mode 100644 index 0000000..2b32238 --- /dev/null +++ b/source3/winbindd/idmap_rfc2307.c @@ -0,0 +1,848 @@ +/* + * Unix SMB/CIFS implementation. + * + * Id mapping using LDAP records as defined in RFC 2307 + * + * The SID<->uid/gid mapping is performed in two steps: 1) Query the + * AD server for the name<->sid mapping. 2) Query an LDAP server + * according to RFC 2307 for the name<->uid/gid mapping. + * + * Copyright (C) Christof Schmitt 2012,2013 + * + * 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.h" +#include "winbindd_ads.h" +#include "idmap.h" +#include "smbldap.h" +#include "nsswitch/winbind_client.h" +#include "lib/winbind_util.h" +#include "libcli/security/dom_sid.h" +#include "lib/global_contexts.h" + +/* + * Config and connection info per domain. + */ +struct idmap_rfc2307_context { + const char *bind_path_user; + const char *bind_path_group; + const char *ldap_domain; + bool user_cn; + const char *realm; + + /* + * Pointer to ldap struct in ads or smbldap_state, has to be + * updated after connecting to server + */ + LDAP *ldap; + + /* Optional function to check connection to server */ + NTSTATUS (*check_connection)(struct idmap_domain *dom); + + /* Issue ldap query */ + NTSTATUS (*search)(struct idmap_rfc2307_context *ctx, + const char *bind_path, const char *expr, + const char **attrs, LDAPMessage **res); + + /* Access to LDAP in AD server */ + ADS_STRUCT *ads; + + /* Access to stand-alone LDAP server */ + struct smbldap_state *smbldap_state; +}; + +/* + * backend functions for LDAP queries through ADS + */ + +static NTSTATUS idmap_rfc2307_ads_check_connection(struct idmap_domain *dom) +{ + struct idmap_rfc2307_context *ctx; + const char *dom_name = dom->name; + ADS_STATUS status; + + DEBUG(10, ("ad_idmap_cached_connection: called for domain '%s'\n", + dom->name)); + + ctx = talloc_get_type(dom->private_data, struct idmap_rfc2307_context); + dom_name = ctx->ldap_domain ? ctx->ldap_domain : dom->name; + + status = ads_idmap_cached_connection(dom_name, ctx, &ctx->ads); + if (ADS_ERR_OK(status)) { + ctx->ldap = ctx->ads->ldap.ld; + } else { + DEBUG(1, ("Could not connect to domain %s: %s\n", dom->name, + ads_errstr(status))); + } + + return ads_ntstatus(status); +} + +static NTSTATUS idmap_rfc2307_ads_search(struct idmap_rfc2307_context *ctx, + const char *bind_path, + const char *expr, + const char **attrs, + LDAPMessage **result) +{ + ADS_STATUS status; + + status = ads_do_search_retry(ctx->ads, bind_path, + LDAP_SCOPE_SUBTREE, expr, attrs, result); + + if (!ADS_ERR_OK(status)) { + return ads_ntstatus(status); + } + + ctx->ldap = ctx->ads->ldap.ld; + return ads_ntstatus(status); +} + +static NTSTATUS idmap_rfc2307_init_ads(struct idmap_rfc2307_context *ctx, + const char *domain_name) +{ + const char *ldap_domain; + + ctx->search = idmap_rfc2307_ads_search; + ctx->check_connection = idmap_rfc2307_ads_check_connection; + + ldap_domain = idmap_config_const_string(domain_name, "ldap_domain", + NULL); + if (ldap_domain) { + ctx->ldap_domain = talloc_strdup(ctx, ldap_domain); + if (ctx->ldap_domain == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + return NT_STATUS_OK; +} + +/* + * backend function for LDAP queries through stand-alone LDAP server + */ + +static NTSTATUS idmap_rfc2307_ldap_search(struct idmap_rfc2307_context *ctx, + const char *bind_path, + const char *expr, + const char **attrs, + LDAPMessage **result) +{ + int ret; + + ret = smbldap_search(ctx->smbldap_state, bind_path, LDAP_SCOPE_SUBTREE, + expr, attrs, 0, result); + ctx->ldap = smbldap_get_ldap(ctx->smbldap_state); + + if (ret == LDAP_SUCCESS) { + return NT_STATUS_OK; + } + + return NT_STATUS_LDAP(ret); +} + +static bool idmap_rfc2307_get_uint32(LDAP *ldap, LDAPMessage *entry, + const char *field, uint32_t *value) +{ + bool b; + char str[20]; + + b = smbldap_get_single_attribute(ldap, entry, field, str, sizeof(str)); + + if (b) { + *value = atoi(str); + } + + return b; +} + +static NTSTATUS idmap_rfc2307_init_ldap(struct idmap_rfc2307_context *ctx, + const char *domain_name) +{ + NTSTATUS ret; + char *url; + char *secret = NULL; + const char *ldap_url, *user_dn; + TALLOC_CTX *mem_ctx = ctx; + + ldap_url = idmap_config_const_string(domain_name, "ldap_url", NULL); + if (!ldap_url) { + DEBUG(1, ("ERROR: missing idmap ldap url\n")); + return NT_STATUS_UNSUCCESSFUL; + } + + url = talloc_strdup(talloc_tos(), ldap_url); + + user_dn = idmap_config_const_string(domain_name, "ldap_user_dn", NULL); + if (user_dn) { + secret = idmap_fetch_secret("ldap", domain_name, user_dn); + if (!secret) { + ret = NT_STATUS_ACCESS_DENIED; + goto done; + } + } + + /* assume anonymous if we don't have a specified user */ + ret = smbldap_init(mem_ctx, global_event_context(), url, + (user_dn == NULL), user_dn, secret, + &ctx->smbldap_state); + BURN_FREE_STR(secret); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("ERROR: smbldap_init (%s) failed!\n", url)); + goto done; + } + + ctx->search = idmap_rfc2307_ldap_search; + +done: + talloc_free(url); + return ret; +} + +/* + * common code for stand-alone LDAP and ADS + */ + +static void idmap_rfc2307_map_sid_results(struct idmap_rfc2307_context *ctx, + TALLOC_CTX *mem_ctx, + struct id_map **ids, + LDAPMessage *result, + const char *dom_name, + const char **attrs, int type) +{ + int count, i; + LDAPMessage *entry; + + count = ldap_count_entries(ctx->ldap, result); + + for (i = 0; i < count; i++) { + char *name; + struct dom_sid sid; + enum lsa_SidType lsa_type; + struct id_map *map; + uint32_t id; + bool b; + + if (i == 0) { + entry = ldap_first_entry(ctx->ldap, result); + } else { + entry = ldap_next_entry(ctx->ldap, entry); + } + if (!entry) { + DEBUG(2, ("Unable to fetch entry.\n")); + break; + } + + name = smbldap_talloc_single_attribute(ctx->ldap, entry, + attrs[0], mem_ctx); + if (!name) { + DEBUG(1, ("Could not get user name\n")); + continue; + } + + b = idmap_rfc2307_get_uint32(ctx->ldap, entry, attrs[1], &id); + if (!b) { + DEBUG(1, ("Could not pull id for record %s\n", name)); + continue; + } + + map = idmap_find_map_by_id(ids, type, id); + if (!map) { + DEBUG(1, ("Could not find id %d, name %s\n", id, name)); + continue; + } + + if (ctx->realm != NULL) { + /* Strip @realm from user or group name */ + char *delim; + + delim = strchr(name, '@'); + if (delim) { + *delim = '\0'; + } + } + + /* by default calls to winbindd are disabled + the following call will not recurse so this is safe */ + (void)winbind_on(); + /* Lookup name from PDC using lsa_lookup_names() */ + b = winbind_lookup_name(dom_name, name, &sid, &lsa_type); + (void)winbind_off(); + + if (!b) { + DEBUG(1, ("SID lookup failed for id %d, %s\n", + id, name)); + continue; + } + + if (type == ID_TYPE_UID && lsa_type != SID_NAME_USER) { + DEBUG(1, ("Wrong type %d for user name %s\n", + type, name)); + continue; + } + + if (type == ID_TYPE_GID && lsa_type != SID_NAME_DOM_GRP && + lsa_type != SID_NAME_ALIAS && + lsa_type != SID_NAME_WKN_GRP) { + DEBUG(1, ("Wrong type %d for group name %s\n", + type, name)); + continue; + } + + map->status = ID_MAPPED; + sid_copy(map->sid, &sid); + } +} + +/* + * Map unixids to names and then to sids. + */ +static NTSTATUS idmap_rfc2307_unixids_to_sids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct idmap_rfc2307_context *ctx; + char *fltr_usr = NULL, *fltr_grp = NULL; + TALLOC_CTX *mem_ctx; + int cnt_usr = 0, cnt_grp = 0, idx = 0, bidx = 0; + LDAPMessage *result = NULL; + NTSTATUS ret; + + ctx = talloc_get_type(dom->private_data, struct idmap_rfc2307_context); + mem_ctx = talloc_new(ctx); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + + if (ctx->check_connection) { + ret = ctx->check_connection(dom); + if (!NT_STATUS_IS_OK(ret)) { + goto out; + } + } + +again: + bidx = idx; + + if (!fltr_usr) { + /* prepare new user query, see getpwuid() in RFC2307 */ + fltr_usr = talloc_asprintf(mem_ctx, + "(&(objectClass=posixAccount)(|"); + } + + if (!fltr_grp) { + /* prepare new group query, see getgrgid() in RFC2307 */ + fltr_grp = talloc_asprintf(mem_ctx, + "(&(objectClass=posixGroup)(|"); + } + + if (!fltr_usr || !fltr_grp) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + + while (cnt_usr < IDMAP_LDAP_MAX_IDS && + cnt_grp < IDMAP_LDAP_MAX_IDS && ids[idx]) { + + switch (ids[idx]->xid.type) { + case ID_TYPE_UID: + fltr_usr = talloc_asprintf_append_buffer(fltr_usr, + "(uidNumber=%d)", ids[idx]->xid.id); + cnt_usr++; + break; + case ID_TYPE_GID: + fltr_grp = talloc_asprintf_append_buffer(fltr_grp, + "(gidNumber=%d)", ids[idx]->xid.id); + cnt_grp++; + break; + default: + DEBUG(3, ("Error: unknown ID type %d\n", + ids[idx]->xid.type)); + ret = NT_STATUS_UNSUCCESSFUL; + goto out; + } + + if (!fltr_usr || !fltr_grp) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + + idx++; + } + + if (cnt_usr == IDMAP_LDAP_MAX_IDS || (cnt_usr != 0 && !ids[idx])) { + const char *attrs[] = { NULL, /* uid or cn */ + "uidNumber", + NULL }; + + fltr_usr = talloc_strdup_append(fltr_usr, "))"); + if (!fltr_usr) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + + attrs[0] = ctx->user_cn ? "cn" : "uid"; + ret = ctx->search(ctx, ctx->bind_path_user, fltr_usr, attrs, + &result); + if (!NT_STATUS_IS_OK(ret)) { + goto out; + } + + idmap_rfc2307_map_sid_results(ctx, mem_ctx, &ids[bidx], result, + dom->name, attrs, ID_TYPE_UID); + cnt_usr = 0; + TALLOC_FREE(fltr_usr); + } + + if (cnt_grp == IDMAP_LDAP_MAX_IDS || (cnt_grp != 0 && !ids[idx])) { + const char *attrs[] = { "cn", "gidNumber", NULL }; + + fltr_grp = talloc_strdup_append(fltr_grp, "))"); + if (!fltr_grp) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + ret = ctx->search(ctx, ctx->bind_path_group, fltr_grp, attrs, + &result); + if (!NT_STATUS_IS_OK(ret)) { + goto out; + } + + idmap_rfc2307_map_sid_results(ctx, mem_ctx, &ids[bidx], result, + dom->name, attrs, ID_TYPE_GID); + cnt_grp = 0; + TALLOC_FREE(fltr_grp); + } + + if (ids[idx]) { + goto again; + } + + ret = NT_STATUS_OK; + +out: + talloc_free(mem_ctx); + return ret; +} + +struct idmap_rfc2307_map { + struct id_map *map; + const char *name; + enum id_type type; +}; + +/* + * Lookup names for SIDS and store the data in the local mapping + * array. + */ +static NTSTATUS idmap_rfc_2307_sids_to_names(TALLOC_CTX *mem_ctx, + struct id_map **ids, + struct idmap_rfc2307_map *maps, + struct idmap_rfc2307_context *ctx) +{ + int i; + + for (i = 0; ids[i]; i++) { + const char *domain, *name; + enum lsa_SidType lsa_type; + struct id_map *id = ids[i]; + struct idmap_rfc2307_map *map = &maps[i]; + struct dom_sid_buf buf; + bool b; + + /* by default calls to winbindd are disabled + the following call will not recurse so this is safe */ + (void)winbind_on(); + b = winbind_lookup_sid(mem_ctx, ids[i]->sid, &domain, &name, + &lsa_type); + (void)winbind_off(); + + if (!b) { + DEBUG(1, ("Lookup sid %s failed.\n", + dom_sid_str_buf(ids[i]->sid, &buf))); + continue; + } + + switch(lsa_type) { + case SID_NAME_USER: + id->xid.type = map->type = ID_TYPE_UID; + if (ctx->user_cn && ctx->realm != NULL) { + name = talloc_asprintf(mem_ctx, "%s@%s", + name, ctx->realm); + } + id->xid.type = map->type = ID_TYPE_UID; + break; + + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + if (ctx->realm != NULL) { + name = talloc_asprintf(mem_ctx, "%s@%s", + name, ctx->realm); + } + id->xid.type = map->type = ID_TYPE_GID; + break; + + default: + DEBUG(1, ("Unknown lsa type %d for sid %s\n", + lsa_type, + dom_sid_str_buf(id->sid, &buf))); + id->status = ID_UNMAPPED; + continue; + } + + map->map = id; + id->status = ID_UNKNOWN; + map->name = strupper_talloc(mem_ctx, name); + + if (!map->name) { + return NT_STATUS_NO_MEMORY; + } + } + + return NT_STATUS_OK; +} + +/* + * Find id_map entry by looking up the name in the internal + * mapping array. + */ +static struct id_map* idmap_rfc2307_find_map(struct idmap_rfc2307_map *maps, + enum id_type type, + const char *name) +{ + int i; + + DEBUG(10, ("Looking for name %s, type %d\n", name, type)); + + for (i = 0; maps[i].map != NULL; i++) { + DEBUG(10, ("Entry %d: name %s, type %d\n", + i, maps[i].name, maps[i].type)); + if (type == maps[i].type && strcmp(name, maps[i].name) == 0) { + return maps[i].map; + } + } + + return NULL; +} + +static void idmap_rfc2307_map_xid_results(struct idmap_rfc2307_context *ctx, + TALLOC_CTX *mem_ctx, + struct idmap_rfc2307_map *maps, + LDAPMessage *result, + struct idmap_domain *dom, + const char **attrs, enum id_type type) +{ + int count, i; + LDAPMessage *entry; + + count = ldap_count_entries(ctx->ldap, result); + + for (i = 0; i < count; i++) { + uint32_t id; + char *name; + bool b; + struct id_map *id_map; + + if (i == 0) { + entry = ldap_first_entry(ctx->ldap, result); + } else { + entry = ldap_next_entry(ctx->ldap, entry); + } + if (!entry) { + DEBUG(2, ("Unable to fetch entry.\n")); + break; + } + + name = smbldap_talloc_single_attribute(ctx->ldap, entry, + attrs[0], mem_ctx); + if (!name) { + DEBUG(1, ("Could not get user name\n")); + continue; + } + + b = idmap_rfc2307_get_uint32(ctx->ldap, entry, attrs[1], &id); + if (!b) { + DEBUG(5, ("Could not pull id for record %s\n", name)); + continue; + } + + if (!idmap_unix_id_is_in_range(id, dom)) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u).\n", + id, dom->low_id, dom->high_id)); + continue; + } + + if (!strupper_m(name)) { + DEBUG(5, ("Could not convert %s to uppercase\n", name)); + continue; + } + id_map = idmap_rfc2307_find_map(maps, type, name); + if (!id_map) { + DEBUG(0, ("Could not find mapping entry for name %s\n", + name)); + continue; + } + + id_map->xid.id = id; + id_map->status = ID_MAPPED; + } +} + +/* + * Map sids to names and then to unixids. + */ +static NTSTATUS idmap_rfc2307_sids_to_unixids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct idmap_rfc2307_context *ctx; + TALLOC_CTX *mem_ctx; + struct idmap_rfc2307_map *int_maps; + int cnt_usr = 0, cnt_grp = 0, idx = 0; + char *fltr_usr = NULL, *fltr_grp = NULL; + NTSTATUS ret; + int i; + + ctx = talloc_get_type(dom->private_data, struct idmap_rfc2307_context); + mem_ctx = talloc_new(talloc_tos()); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + + if (ctx->check_connection) { + ret = ctx->check_connection(dom); + if (!NT_STATUS_IS_OK(ret)) { + goto out; + } + } + + for (i = 0; ids[i]; i++); + int_maps = talloc_zero_array(mem_ctx, struct idmap_rfc2307_map, i); + if (!int_maps) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + + ret = idmap_rfc_2307_sids_to_names(mem_ctx, ids, int_maps, ctx); + if (!NT_STATUS_IS_OK(ret)) { + goto out; + } + +again: + if (!fltr_usr) { + /* prepare new user query, see getpwuid() in RFC2307 */ + fltr_usr = talloc_asprintf(mem_ctx, + "(&(objectClass=posixAccount)(|"); + } + + if (!fltr_grp) { + /* prepare new group query, see getgrgid() in RFC2307 */ + fltr_grp = talloc_asprintf(mem_ctx, + "(&(objectClass=posixGroup)(|"); + } + + if (!fltr_usr || !fltr_grp) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + + while (cnt_usr < IDMAP_LDAP_MAX_IDS && + cnt_grp < IDMAP_LDAP_MAX_IDS && ids[idx]) { + struct id_map *id = ids[idx]; + struct idmap_rfc2307_map *map = &int_maps[idx]; + + switch(id->xid.type) { + case ID_TYPE_UID: + fltr_usr = talloc_asprintf_append_buffer(fltr_usr, + "(%s=%s)", (ctx->user_cn ? "cn" : "uid"), + map->name); + cnt_usr++; + break; + + case ID_TYPE_GID: + fltr_grp = talloc_asprintf_append_buffer(fltr_grp, + "(cn=%s)", map->name); + cnt_grp++; + break; + + default: + break; + } + + if (!fltr_usr || !fltr_grp) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + + idx++; + } + + if (cnt_usr == IDMAP_LDAP_MAX_IDS || (cnt_usr != 0 && !ids[idx])) { + const char *attrs[] = { NULL, /* uid or cn */ + "uidNumber", + NULL }; + LDAPMessage *result; + + fltr_usr = talloc_strdup_append(fltr_usr, "))"); + if (!fltr_usr) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + + attrs[0] = ctx->user_cn ? "cn" : "uid"; + ret = ctx->search(ctx, ctx->bind_path_user, fltr_usr, attrs, + &result); + if (!NT_STATUS_IS_OK(ret)) { + goto out; + } + + idmap_rfc2307_map_xid_results(ctx, mem_ctx, int_maps, + result, dom, attrs, ID_TYPE_UID); + + cnt_usr = 0; + TALLOC_FREE(fltr_usr); + } + + if (cnt_grp == IDMAP_LDAP_MAX_IDS || (cnt_grp != 0 && !ids[idx])) { + const char *attrs[] = {"cn", "gidNumber", NULL }; + LDAPMessage *result; + + fltr_grp = talloc_strdup_append(fltr_grp, "))"); + if (!fltr_grp) { + ret = NT_STATUS_NO_MEMORY; + goto out; + } + + ret = ctx->search(ctx, ctx->bind_path_group, fltr_grp, attrs, + &result); + if (!NT_STATUS_IS_OK(ret)) { + goto out; + } + + idmap_rfc2307_map_xid_results(ctx, mem_ctx, int_maps, result, + dom, attrs, ID_TYPE_GID); + cnt_grp = 0; + TALLOC_FREE(fltr_grp); + } + + if (ids[idx]) { + goto again; + } + + ret = NT_STATUS_OK; + +out: + talloc_free(mem_ctx); + return ret; +} + +static int idmap_rfc2307_context_destructor(struct idmap_rfc2307_context *ctx) +{ + TALLOC_FREE(ctx->ads); + + if (ctx->smbldap_state != NULL) { + smbldap_free_struct(&ctx->smbldap_state); + } + + return 0; +} + +static NTSTATUS idmap_rfc2307_initialize(struct idmap_domain *domain) +{ + struct idmap_rfc2307_context *ctx; + const char *bind_path_user, *bind_path_group, *ldap_server, *realm; + NTSTATUS status; + + ctx = talloc_zero(domain, struct idmap_rfc2307_context); + if (ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(ctx, idmap_rfc2307_context_destructor); + + bind_path_user = idmap_config_const_string( + domain->name, "bind_path_user", NULL); + if (bind_path_user == NULL) { + status = NT_STATUS_INVALID_PARAMETER; + goto err; + } + ctx->bind_path_user = talloc_strdup(ctx, bind_path_user); + if (ctx->bind_path_user == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err; + } + + bind_path_group = idmap_config_const_string( + domain->name, "bind_path_group", NULL); + if (bind_path_group == NULL) { + status = NT_STATUS_INVALID_PARAMETER; + goto err; + } + ctx->bind_path_group = talloc_strdup(ctx, bind_path_group); + if (ctx->bind_path_group == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err; + } + + ldap_server = idmap_config_const_string( + domain->name, "ldap_server", NULL); + if (!ldap_server) { + status = NT_STATUS_INVALID_PARAMETER; + goto err; + } + + if (strcmp(ldap_server, "stand-alone") == 0) { + status = idmap_rfc2307_init_ldap(ctx, domain->name); + + } else if (strcmp(ldap_server, "ad") == 0) { + status = idmap_rfc2307_init_ads(ctx, domain->name); + + } else { + status = NT_STATUS_INVALID_PARAMETER; + } + + if (!NT_STATUS_IS_OK(status)) { + goto err; + } + + realm = idmap_config_const_string(domain->name, "realm", NULL); + if (realm) { + ctx->realm = talloc_strdup(ctx, realm); + if (ctx->realm == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err; + } + } + + ctx->user_cn = idmap_config_bool(domain->name, "user_cn", false); + + domain->private_data = ctx; + return NT_STATUS_OK; + +err: + talloc_free(ctx); + return status; +} + +static const struct idmap_methods rfc2307_methods = { + .init = idmap_rfc2307_initialize, + .unixids_to_sids = idmap_rfc2307_unixids_to_sids, + .sids_to_unixids = idmap_rfc2307_sids_to_unixids, +}; + +static_decl_idmap; +NTSTATUS idmap_rfc2307_init(TALLOC_CTX *ctx) +{ + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "rfc2307", + &rfc2307_methods); +} diff --git a/source3/winbindd/idmap_rid.c b/source3/winbindd/idmap_rid.c new file mode 100644 index 0000000..4fecf22 --- /dev/null +++ b/source3/winbindd/idmap_rid.c @@ -0,0 +1,182 @@ +/* + * idmap_rid: static map between Active Directory/NT RIDs and RFC 2307 accounts + * Copyright (C) Guenther Deschner, 2004 + * Copyright (C) Sumit Bose, 2004 + * + * 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.h" +#include "idmap.h" +#include "../libcli/security/dom_sid.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +struct idmap_rid_context { + uint32_t base_rid; +}; + +/****************************************************************************** + compat params can't be used because of the completely different way + we support multiple domains in the new idmap + *****************************************************************************/ + +static NTSTATUS idmap_rid_initialize(struct idmap_domain *dom) +{ + struct idmap_rid_context *ctx; + + ctx = talloc_zero(dom, struct idmap_rid_context); + if (ctx == NULL) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + ctx->base_rid = idmap_config_int(dom->name, "base_rid", 0); + + dom->private_data = ctx; + + return NT_STATUS_OK; +} + +static NTSTATUS idmap_rid_id_to_sid(struct idmap_domain *dom, struct id_map *map) +{ + struct idmap_rid_context *ctx; + + ctx = talloc_get_type(dom->private_data, struct idmap_rid_context); + + /* apply filters before checking */ + if (!idmap_unix_id_is_in_range(map->xid.id, dom)) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n", + map->xid.id, dom->low_id, dom->high_id)); + return NT_STATUS_NONE_MAPPED; + } + + if (is_null_sid(&dom->dom_sid)) { + DBG_INFO("idmap domain '%s' without SID\n", dom->name); + return NT_STATUS_NONE_MAPPED; + } + + sid_compose(map->sid, &dom->dom_sid, + map->xid.id - dom->low_id + ctx->base_rid); + + map->status = ID_MAPPED; + map->xid.type = ID_TYPE_BOTH; + + return NT_STATUS_OK; +} + +/********************************** + Single sid to id lookup function. +**********************************/ + +static NTSTATUS idmap_rid_sid_to_id(struct idmap_domain *dom, struct id_map *map) +{ + uint32_t rid; + struct idmap_rid_context *ctx; + + ctx = talloc_get_type(dom->private_data, struct idmap_rid_context); + + sid_peek_rid(map->sid, &rid); + map->xid.id = rid - ctx->base_rid + dom->low_id; + map->xid.type = ID_TYPE_BOTH; + + /* apply filters before returning result */ + + if (!idmap_unix_id_is_in_range(map->xid.id, dom)) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n", + map->xid.id, dom->low_id, dom->high_id)); + map->status = ID_UNMAPPED; + return NT_STATUS_NONE_MAPPED; + } + + map->status = ID_MAPPED; + + return NT_STATUS_OK; +} + +/********************************** + lookup a set of unix ids. +**********************************/ + +static NTSTATUS idmap_rid_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids) +{ + NTSTATUS ret; + int i; + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + + for (i = 0; ids[i]; i++) { + + ret = idmap_rid_id_to_sid(dom, ids[i]); + + if (( ! NT_STATUS_IS_OK(ret)) && + ( ! NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) { + /* some fatal error occurred, log it */ + DBG_NOTICE("Unexpected error resolving an ID " + "(%d): %s\n", ids[i]->xid.id, + nt_errstr(ret)); + } + } + + return NT_STATUS_OK; +} + +/********************************** + lookup a set of sids. +**********************************/ + +static NTSTATUS idmap_rid_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids) +{ + NTSTATUS ret; + int i; + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + + for (i = 0; ids[i]; i++) { + + ret = idmap_rid_sid_to_id(dom, ids[i]); + + if (( ! NT_STATUS_IS_OK(ret)) && + ( ! NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) { + struct dom_sid_buf buf; + /* some fatal error occurred, log it */ + DEBUG(3, ("Unexpected error resolving a SID (%s)\n", + dom_sid_str_buf(ids[i]->sid, &buf))); + } + } + + return NT_STATUS_OK; +} + +static const struct idmap_methods rid_methods = { + .init = idmap_rid_initialize, + .unixids_to_sids = idmap_rid_unixids_to_sids, + .sids_to_unixids = idmap_rid_sids_to_unixids, +}; + +static_decl_idmap; +NTSTATUS idmap_rid_init(TALLOC_CTX *ctx) +{ + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "rid", &rid_methods); +} + diff --git a/source3/winbindd/idmap_rw.c b/source3/winbindd/idmap_rw.c new file mode 100644 index 0000000..71bfc14 --- /dev/null +++ b/source3/winbindd/idmap_rw.c @@ -0,0 +1,109 @@ +/* + * Unix SMB/CIFS implementation. + * + * ID mapping: abstract r/w new-mapping mechanism + * + * Copyright (C) Michael Adam 2010 + * + * 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.h" +#include "idmap.h" +#include "idmap_rw.h" +#include "libcli/security/dom_sid.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +NTSTATUS idmap_rw_new_mapping(struct idmap_domain *dom, + struct idmap_rw_ops *ops, + struct id_map *map) +{ + struct dom_sid_buf buf; + NTSTATUS status; + + if (map == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (map->sid == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (map->xid.type) { + case ID_TYPE_NOT_SPECIFIED: + /* + * We need to know if we need a user or group mapping. + * Ask the winbindd parent to provide a valid type hint. + */ + DBG_INFO("%s ID_TYPE_NOT_SPECIFIED => ID_REQUIRE_TYPE\n", + dom_sid_str_buf(map->sid, &buf)); + map->status = ID_REQUIRE_TYPE; + return NT_STATUS_SOME_NOT_MAPPED; + + case ID_TYPE_BOTH: + /* + * For now we still require + * an explicit type as hint + * and don't support ID_TYPE_BOTH + */ + DBG_INFO("%s ID_TYPE_BOTH => ID_REQUIRE_TYPE\n", + dom_sid_str_buf(map->sid, &buf)); + map->status = ID_REQUIRE_TYPE; + return NT_STATUS_SOME_NOT_MAPPED; + + case ID_TYPE_UID: + break; + + case ID_TYPE_GID: + break; + + default: + return NT_STATUS_INVALID_PARAMETER; + } + + status = ops->get_new_id(dom, &map->xid); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not allocate id: %s\n", nt_errstr(status))); + return status; + } + + DEBUG(10, ("Setting mapping: %s <-> %s %lu\n", + dom_sid_str_buf(map->sid, &buf), + (map->xid.type == ID_TYPE_UID) ? "UID" : "GID", + (unsigned long)map->xid.id)); + + map->status = ID_MAPPED; + status = ops->set_mapping(dom, map); + + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) { + struct id_map *ids[2]; + DEBUG(5, ("Mapping for %s exists - retrying to map sid\n", + dom_sid_str_buf(map->sid, &buf))); + ids[0] = map; + ids[1] = NULL; + status = dom->methods->sids_to_unixids(dom, ids); + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not store the new mapping: %s\n", + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/idmap_rw.h b/source3/winbindd/idmap_rw.h new file mode 100644 index 0000000..72389e7 --- /dev/null +++ b/source3/winbindd/idmap_rw.h @@ -0,0 +1,56 @@ +/* + * Unix SMB/CIFS implementation. + * + * ID mapping: abstract r/w new-mapping mechanism + * + * Copyright (C) Michael Adam 2010 + * + * 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/>. + */ + +/* + * This module implements the abstract logic for creating a new + * SID<->Unix-ID mapping. It can be used by idmap backends that + * need to create mappings for unmapped SIDs upon request. + */ + +#ifndef _IDMAP_RW_H_ +#define _IDMAP_RW_H_ + +#include "includes.h" +#include "idmap.h" + +struct idmap_rw_ops { + NTSTATUS (*get_new_id)(struct idmap_domain *dom, struct unixid *id); + NTSTATUS (*set_mapping)(struct idmap_domain *dom, + const struct id_map *map); +}; + +/** + * This is the abstract mechanism of creating a new mapping + * for a given SID. It is meant to be called called from an + * allocating backend from within idmap_<backend>_sids_to_unixids(). + * It expects map->sid and map->xid.type to be set. + * Upon success, the new mapping is stored by the backend and + * map contains the new mapped xid. + * + * The caller has to take care of the necessary steps to + * guarantee atomicity of the operation, e.g. wrapping + * this call in a transaction if available. + */ +NTSTATUS idmap_rw_new_mapping(struct idmap_domain *dom, + struct idmap_rw_ops *ops, + struct id_map *map); + +#endif /* _IDMAP_RW_H_ */ diff --git a/source3/winbindd/idmap_script.c b/source3/winbindd/idmap_script.c new file mode 100644 index 0000000..bb89175 --- /dev/null +++ b/source3/winbindd/idmap_script.c @@ -0,0 +1,650 @@ +/* + Unix SMB/CIFS implementation. + + idmap script backend, used for Samba setups where you need to map SIDs to + specific UIDs/GIDs. + + Copyright (C) Richard Sharpe 2014. + + This is heavily based upon idmap_tdb2.c, which is: + + Copyright (C) Tim Potter 2000 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Jeremy Allison 2006 + Copyright (C) Simo Sorce 2003-2006 + Copyright (C) Michael Adam 2009-2010 + + 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 "system/filesys.h" +#include "winbindd.h" +#include "idmap.h" +#include "idmap_rw.h" +#include "../libcli/security/dom_sid.h" +#include "lib/util_file.h" +#include "lib/util/tevent_unix.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +struct idmap_script_context { + const char *script; /* script to provide idmaps */ +}; + +/* + run a script to perform a mapping + + The script should accept the following command lines: + + SIDTOID S-1-xxxx -> XID:<id> | ERR:<str> + SIDTOID S-1-xxxx -> UID:<id> | ERR:<str> + SIDTOID S-1-xxxx -> GID:<id> | ERR:<str> + IDTOSID XID xxxx -> SID:<sid> | ERR:<str> + IDTOSID UID xxxx -> SID:<sid> | ERR:<str> + IDTOSID GID xxxx -> SID:<sid> | ERR:<str> + + where XID means both a UID and a GID. This is the case for ID_TYPE_BOTH. + + TODO: Needs more validation ... like that we got a UID when we asked for one. + */ + +struct idmap_script_xid2sid_state { + char **argl; + size_t idx; + uint8_t *out; +}; + +static void idmap_script_xid2sid_done(struct tevent_req *subreq); + +static struct tevent_req *idmap_script_xid2sid_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct unixid xid, const char *script, size_t idx) +{ + struct tevent_req *req, *subreq; + struct idmap_script_xid2sid_state *state; + char key; + + req = tevent_req_create(mem_ctx, &state, + struct idmap_script_xid2sid_state); + if (req == NULL) { + return NULL; + } + state->idx = idx; + + switch (xid.type) { + case ID_TYPE_UID: + key = 'U'; + break; + case ID_TYPE_GID: + key = 'G'; + break; + case ID_TYPE_BOTH: + key = 'X'; + break; + default: + DBG_WARNING("INVALID unix ID type: 0x02%x\n", xid.type); + tevent_req_error(req, EINVAL); + return tevent_req_post(req, ev); + } + + state->argl = str_list_make_empty(state); + str_list_add_printf(&state->argl, "%s", script); + str_list_add_printf(&state->argl, "IDTOSID"); + str_list_add_printf(&state->argl, "%cID", key); + str_list_add_printf(&state->argl, "%lu", (unsigned long)xid.id); + if (tevent_req_nomem(state->argl, req)) { + return tevent_req_post(req, ev); + } + + subreq = file_ploadv_send(state, ev, state->argl, 1024); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, idmap_script_xid2sid_done, req); + return req; +} + +static void idmap_script_xid2sid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct idmap_script_xid2sid_state *state = tevent_req_data( + req, struct idmap_script_xid2sid_state); + int ret; + + ret = file_ploadv_recv(subreq, state, &state->out); + TALLOC_FREE(subreq); + if (tevent_req_error(req, ret)) { + return; + } + tevent_req_done(req); +} + +static int idmap_script_xid2sid_recv(struct tevent_req *req, size_t *idx, + enum id_mapping *status, + struct dom_sid *sid) +{ + struct idmap_script_xid2sid_state *state = tevent_req_data( + req, struct idmap_script_xid2sid_state); + char *out = (char *)state->out; + size_t out_size = talloc_get_size(out); + int err; + + if (tevent_req_is_unix_error(req, &err)) { + return err; + } + + if (out_size == 0) { + goto unmapped; + } + if (state->out[out_size-1] != '\0') { + goto unmapped; + } + + *idx = state->idx; + + if ((strncmp(out, "SID:S-", 6) != 0) || + !dom_sid_parse(out+4, sid)) { + DBG_WARNING("Bad sid from script: %s\n", out); + goto unmapped; + } + + *status = ID_MAPPED; + return 0; + +unmapped: + *sid = (struct dom_sid) {0}; + *status = ID_UNMAPPED; + return 0; +} + +struct idmap_script_xids2sids_state { + struct id_map **ids; + size_t num_ids; + size_t num_done; +}; + +static void idmap_script_xids2sids_done(struct tevent_req *subreq); + +static struct tevent_req *idmap_script_xids2sids_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct id_map **ids, size_t num_ids, const char *script) +{ + struct tevent_req *req; + struct idmap_script_xids2sids_state *state; + size_t i; + + req = tevent_req_create(mem_ctx, &state, + struct idmap_script_xids2sids_state); + if (req == NULL) { + return NULL; + } + state->ids = ids; + state->num_ids = num_ids; + + if (state->num_ids == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + for (i=0; i<num_ids; i++) { + struct tevent_req *subreq; + + subreq = idmap_script_xid2sid_send( + state, ev, ids[i]->xid, script, i); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, idmap_script_xids2sids_done, + req); + } + + return req; +} + +static void idmap_script_xids2sids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct idmap_script_xids2sids_state *state = tevent_req_data( + req, struct idmap_script_xids2sids_state); + size_t idx = 0; + enum id_mapping status = ID_UNKNOWN; + struct dom_sid sid = {0}; + int ret; + + ret = idmap_script_xid2sid_recv(subreq, &idx, &status, &sid); + TALLOC_FREE(subreq); + if (tevent_req_error(req, ret)) { + return; + } + + if (idx >= state->num_ids) { + tevent_req_error(req, EINVAL); + return; + } + + state->ids[idx]->status = status; + + state->ids[idx]->sid = dom_sid_dup(state->ids, &sid); + if (tevent_req_nomem(state->ids[idx]->sid, req)) { + return; + } + + state->num_done += 1; + + if (state->num_done >= state->num_ids) { + tevent_req_done(req); + } +} + +static int idmap_script_xids2sids_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} + +static int idmap_script_xids2sids(struct id_map **ids, size_t num_ids, + const char *script) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + int ret = ENOMEM; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = idmap_script_xids2sids_send(frame, ev, ids, num_ids, script); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll(req, ev)) { + ret = errno; + goto fail; + } + ret = idmap_script_xids2sids_recv(req); +fail: + TALLOC_FREE(frame); + return ret; +} + +static NTSTATUS idmap_script_unixids_to_sids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct idmap_script_context *ctx = talloc_get_type_abort( + dom->private_data, struct idmap_script_context); + int ret; + size_t i, num_ids, num_mapped; + + DEBUG(10, ("%s called ...\n", __func__)); + /* Init status to avoid surprise ... */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + num_ids = i; + + ret = idmap_script_xids2sids(ids, num_ids, ctx->script); + if (ret != 0) { + DBG_DEBUG("idmap_script_xids2sids returned %s\n", + strerror(ret)); + return map_nt_error_from_unix(ret); + } + + num_mapped = 0; + + for (i = 0; ids[i]; i++) { + if (ids[i]->status == ID_MAPPED) { + num_mapped += 1; + } + } + + if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + if (num_mapped < num_ids) { + return STATUS_SOME_UNMAPPED; + } + return NT_STATUS_OK; +} + +struct idmap_script_sid2xid_state { + char **argl; + size_t idx; + uint8_t *out; +}; + +static void idmap_script_sid2xid_done(struct tevent_req *subreq); + +static struct tevent_req *idmap_script_sid2xid_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + const struct dom_sid *sid, const char *script, size_t idx) +{ + struct tevent_req *req, *subreq; + struct idmap_script_sid2xid_state *state; + struct dom_sid_buf sidbuf; + + req = tevent_req_create(mem_ctx, &state, + struct idmap_script_sid2xid_state); + if (req == NULL) { + return NULL; + } + state->idx = idx; + + state->argl = str_list_make_empty(state); + str_list_add_printf(&state->argl, "%s", script); + str_list_add_printf(&state->argl, "SIDTOID"); + str_list_add_printf( + &state->argl, "%s", dom_sid_str_buf(sid, &sidbuf)); + if (tevent_req_nomem(state->argl, req)) { + return tevent_req_post(req, ev); + } + + subreq = file_ploadv_send(state, ev, state->argl, 1024); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, idmap_script_sid2xid_done, req); + return req; +} + +static void idmap_script_sid2xid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct idmap_script_sid2xid_state *state = tevent_req_data( + req, struct idmap_script_sid2xid_state); + int ret; + + ret = file_ploadv_recv(subreq, state, &state->out); + TALLOC_FREE(subreq); + if (tevent_req_error(req, ret)) { + return; + } + tevent_req_done(req); +} + +static int idmap_script_sid2xid_recv(struct tevent_req *req, + size_t *idx, enum id_mapping *status, + struct unixid *xid) +{ + struct idmap_script_sid2xid_state *state = tevent_req_data( + req, struct idmap_script_sid2xid_state); + char *out = (char *)state->out; + size_t out_size = talloc_get_size(out); + unsigned long v; + int err; + + if (tevent_req_is_unix_error(req, &err)) { + return err; + } + + if (out_size == 0) { + goto unmapped; + } + if (state->out[out_size-1] != '\0') { + goto unmapped; + } + + *idx = state->idx; + + if (sscanf(out, "XID:%lu\n", &v) == 1) { + *xid = (struct unixid) { .id = v, .type = ID_TYPE_BOTH }; + } else if (sscanf(out, "UID:%lu\n", &v) == 1) { + *xid = (struct unixid) { .id = v, .type = ID_TYPE_UID }; + } else if (sscanf(out, "GID:%lu\n", &v) == 1) { + *xid = (struct unixid) { .id = v, .type = ID_TYPE_GID }; + } else { + goto unmapped; + } + + *status = ID_MAPPED; + return 0; + +unmapped: + *xid = (struct unixid) { .id = UINT32_MAX, + .type = ID_TYPE_NOT_SPECIFIED }; + *status = ID_UNMAPPED; + return 0; +} + +struct idmap_script_sids2xids_state { + struct id_map **ids; + size_t num_ids; + size_t num_done; +}; + +static void idmap_script_sids2xids_done(struct tevent_req *subreq); + +static struct tevent_req *idmap_script_sids2xids_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct id_map **ids, size_t num_ids, const char *script) +{ + struct tevent_req *req; + struct idmap_script_sids2xids_state *state; + size_t i; + + req = tevent_req_create(mem_ctx, &state, + struct idmap_script_sids2xids_state); + if (req == NULL) { + return NULL; + } + state->ids = ids; + state->num_ids = num_ids; + + if (state->num_ids == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + for (i=0; i<num_ids; i++) { + struct tevent_req *subreq; + + subreq = idmap_script_sid2xid_send( + state, ev, ids[i]->sid, script, i); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, idmap_script_sids2xids_done, + req); + } + + return req; +} + +static void idmap_script_sids2xids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct idmap_script_sids2xids_state *state = tevent_req_data( + req, struct idmap_script_sids2xids_state); + size_t idx = 0; + enum id_mapping status = ID_UNKNOWN; + struct unixid xid = { .id = UINT32_MAX, + .type = ID_TYPE_NOT_SPECIFIED }; + int ret; + + ret = idmap_script_sid2xid_recv(subreq, &idx, &status, &xid); + TALLOC_FREE(subreq); + if (tevent_req_error(req, ret)) { + return; + } + + if (idx >= state->num_ids) { + tevent_req_error(req, EINVAL); + return; + } + + state->ids[idx]->status = status; + state->ids[idx]->xid = xid; + + state->num_done += 1; + + if (state->num_done >= state->num_ids) { + tevent_req_done(req); + } +} + +static int idmap_script_sids2xids_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} + +static int idmap_script_sids2xids(struct id_map **ids, size_t num_ids, + const char *script) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + int ret = ENOMEM; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = idmap_script_sids2xids_send(frame, ev, ids, num_ids, script); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll(req, ev)) { + ret = errno; + goto fail; + } + ret = idmap_script_sids2xids_recv(req); +fail: + TALLOC_FREE(frame); + return ret; +} + +static NTSTATUS idmap_script_sids_to_unixids(struct idmap_domain *dom, + struct id_map **ids) +{ + struct idmap_script_context *ctx = talloc_get_type_abort( + dom->private_data, struct idmap_script_context); + int ret; + size_t i, num_ids, num_mapped; + + DEBUG(10, ("%s called ...\n", __func__)); + /* Init status to avoid surprise ... */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + num_ids = i; + + ret = idmap_script_sids2xids(ids, num_ids, ctx->script); + if (ret != 0) { + DBG_DEBUG("idmap_script_sids2xids returned %s\n", + strerror(ret)); + return map_nt_error_from_unix(ret); + } + + num_mapped = 0; + + for (i=0; i<num_ids; i++) { + struct id_map *map = ids[i]; + + if ((map->status == ID_MAPPED) && + !idmap_unix_id_is_in_range(map->xid.id, dom)) { + DBG_INFO("Script returned id (%u) out of range " + "(%u - %u). Filtered!\n", + map->xid.id, dom->low_id, dom->high_id); + map->status = ID_UNMAPPED; + } + + if (map->status == ID_MAPPED) { + num_mapped += 1; + } + } + + if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + if (num_mapped < num_ids) { + return STATUS_SOME_UNMAPPED; + } + return NT_STATUS_OK; +} + +/* + * Initialise idmap_script database. + */ +static NTSTATUS idmap_script_db_init(struct idmap_domain *dom) +{ + NTSTATUS ret; + struct idmap_script_context *ctx; + const char * idmap_script = NULL; + const char *ctx_script = NULL; + + DEBUG(10, ("%s called ...\n", __func__)); + + ctx = talloc_zero(dom, struct idmap_script_context); + if (!ctx) { + DEBUG(0, ("Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto failed; + } + + ctx_script = idmap_config_const_string(dom->name, "script", NULL); + + /* Do we even need to handle this? */ + idmap_script = lp_parm_const_string(-1, "idmap", "script", NULL); + if (idmap_script != NULL) { + DEBUG(0, ("Warning: 'idmap:script' is deprecated. " + " Please use 'idmap config * : script' instead!\n")); + } + + if (strequal(dom->name, "*") && ctx_script == NULL) { + /* fall back to idmap:script for backwards compatibility */ + ctx_script = idmap_script; + } + + if (ctx_script) { + DEBUG(1, ("using idmap script '%s'\n", ctx->script)); + /* + * We must ensure this memory is owned by ctx. + * The ctx_script const pointer is a pointer into + * the config file data and may become invalid + * on config file reload. BUG: 13956 + */ + ctx->script = talloc_strdup(ctx, ctx_script); + if (ctx->script == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto failed; + } + } + + dom->private_data = ctx; + dom->read_only = true; /* We do not allocate!*/ + + return NT_STATUS_OK; + +failed: + talloc_free(ctx); + return ret; +} + +static const struct idmap_methods db_methods = { + .init = idmap_script_db_init, + .unixids_to_sids = idmap_script_unixids_to_sids, + .sids_to_unixids = idmap_script_sids_to_unixids, +}; + +static_decl_idmap; +NTSTATUS idmap_script_init(TALLOC_CTX *ctx) +{ + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "script", &db_methods); +} diff --git a/source3/winbindd/idmap_tdb.c b/source3/winbindd/idmap_tdb.c new file mode 100644 index 0000000..77714d9 --- /dev/null +++ b/source3/winbindd/idmap_tdb.c @@ -0,0 +1,434 @@ +/* + Unix SMB/CIFS implementation. + + idmap TDB backend + + Copyright (C) Tim Potter 2000 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Jeremy Allison 2006 + Copyright (C) Simo Sorce 2003-2006 + Copyright (C) Michael Adam 2009-2010 + + 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 "system/filesys.h" +#include "winbindd.h" +#include "idmap.h" +#include "idmap_rw.h" +#include "idmap_tdb_common.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "../libcli/security/security.h" +#include "util_tdb.h" +#include "lib/util/string_wrappers.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +/* idmap version determines auto-conversion - this is the database + structure version specifier. */ + +#define IDMAP_VERSION 2 + +/* High water mark keys */ +#define HWM_GROUP "GROUP HWM" +#define HWM_USER "USER HWM" + +struct convert_fn_state { + struct db_context *db; + bool failed; +}; + +/***************************************************************************** + For idmap conversion: convert one record to new format + Ancient versions (eg 2.2.3a) of winbindd_idmap.tdb mapped DOMAINNAME/rid + instead of the SID. +*****************************************************************************/ +static int convert_fn(struct db_record *rec, void *private_data) +{ + struct winbindd_domain *domain; + char *p; + NTSTATUS status; + struct dom_sid sid; + uint32_t rid; + struct dom_sid_buf keystr; + fstring dom_name; + TDB_DATA key; + TDB_DATA key2; + TDB_DATA value; + struct convert_fn_state *s = (struct convert_fn_state *)private_data; + + key = dbwrap_record_get_key(rec); + + DEBUG(10,("Converting %s\n", (const char *)key.dptr)); + + p = strchr((const char *)key.dptr, '/'); + if (!p) + return 0; + + *p = 0; + fstrcpy(dom_name, (const char *)key.dptr); + *p++ = '/'; + + domain = find_domain_from_name(dom_name); + if (domain == NULL) { + /* We must delete the old record. */ + DEBUG(0,("Unable to find domain %s\n", dom_name )); + DEBUG(0,("deleting record %s\n", (const char *)key.dptr )); + + status = dbwrap_record_delete(rec); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Unable to delete record %s:%s\n", + (const char *)key.dptr, + nt_errstr(status))); + s->failed = true; + return -1; + } + + return 0; + } + + rid = atoi(p); + + sid_compose(&sid, &domain->sid, rid); + + key2 = string_term_tdb_data(dom_sid_str_buf(&sid, &keystr)); + + value = dbwrap_record_get_value(rec); + + status = dbwrap_store(s->db, key2, value, TDB_INSERT); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to add record %s:%s\n", + (const char *)key2.dptr, + nt_errstr(status))); + s->failed = true; + return -1; + } + + status = dbwrap_store(s->db, value, key2, TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to update record %s:%s\n", + (const char *)value.dptr, + nt_errstr(status))); + s->failed = true; + return -1; + } + + status = dbwrap_record_delete(rec); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to delete record %s:%s\n", + (const char *)key.dptr, + nt_errstr(status))); + s->failed = true; + return -1; + } + + return 0; +} + +/***************************************************************************** + Convert the idmap database from an older version. +*****************************************************************************/ + +static bool idmap_tdb_upgrade(struct idmap_domain *dom, struct db_context *db) +{ + int32_t vers; + struct convert_fn_state s; + NTSTATUS status; + + status = dbwrap_fetch_int32_bystring(db, "IDMAP_VERSION", &vers); + if (!NT_STATUS_IS_OK(status)) { + vers = -1; + } + + if (IREV(vers) == IDMAP_VERSION) { + /* Arrggghh ! Bytereversed - make order independent ! */ + /* + * high and low records were created on a + * big endian machine and will need byte-reversing. + */ + + int32_t wm; + + status = dbwrap_fetch_int32_bystring(db, HWM_USER, &wm); + if (!NT_STATUS_IS_OK(status)) { + wm = -1; + } + + if (wm != -1) { + wm = IREV(wm); + } else { + wm = dom->low_id; + } + + status = dbwrap_store_int32_bystring(db, HWM_USER, wm); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Unable to byteswap user hwm in idmap " + "database: %s\n", nt_errstr(status))); + return False; + } + + status = dbwrap_fetch_int32_bystring(db, HWM_GROUP, &wm); + if (!NT_STATUS_IS_OK(status)) { + wm = -1; + } + + if (wm != -1) { + wm = IREV(wm); + } else { + wm = dom->low_id; + } + + status = dbwrap_store_int32_bystring(db, HWM_GROUP, wm); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Unable to byteswap group hwm in idmap " + "database: %s\n", nt_errstr(status))); + return False; + } + } + + s.db = db; + s.failed = false; + + /* the old format stored as DOMAIN/rid - now we store the SID direct */ + status = dbwrap_traverse(db, convert_fn, &s, NULL); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Database traverse failed during conversion\n")); + return false; + } + + if (s.failed) { + DEBUG(0, ("Problem during conversion\n")); + return False; + } + + status = dbwrap_store_int32_bystring(db, "IDMAP_VERSION", + IDMAP_VERSION); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Unable to store idmap version in database: %s\n", + nt_errstr(status))); + return False; + } + + return True; +} + +static NTSTATUS idmap_tdb_init_hwm(struct idmap_domain *dom) +{ + uint32_t low_uid; + uint32_t low_gid; + bool update_uid = false; + bool update_gid = false; + struct idmap_tdb_common_context *ctx; + NTSTATUS status; + + ctx = talloc_get_type(dom->private_data, + struct idmap_tdb_common_context); + + status = dbwrap_fetch_uint32_bystring(ctx->db, HWM_USER, &low_uid); + if (!NT_STATUS_IS_OK(status) || low_uid < dom->low_id) { + update_uid = true; + } + + status = dbwrap_fetch_uint32_bystring(ctx->db, HWM_GROUP, &low_gid); + if (!NT_STATUS_IS_OK(status) || low_gid < dom->low_id) { + update_gid = true; + } + + if (!update_uid && !update_gid) { + return NT_STATUS_OK; + } + + if (dbwrap_transaction_start(ctx->db) != 0) { + DEBUG(0, ("Unable to start upgrade transaction!\n")); + return NT_STATUS_INTERNAL_DB_ERROR; + } + + if (update_uid) { + status = dbwrap_store_uint32_bystring(ctx->db, HWM_USER, + dom->low_id); + if (!NT_STATUS_IS_OK(status)) { + dbwrap_transaction_cancel(ctx->db); + DEBUG(0, ("Unable to initialise user hwm in idmap " + "database: %s\n", nt_errstr(status))); + return NT_STATUS_INTERNAL_DB_ERROR; + } + } + + if (update_gid) { + status = dbwrap_store_uint32_bystring(ctx->db, HWM_GROUP, + dom->low_id); + if (!NT_STATUS_IS_OK(status)) { + dbwrap_transaction_cancel(ctx->db); + DEBUG(0, ("Unable to initialise group hwm in idmap " + "database: %s\n", nt_errstr(status))); + return NT_STATUS_INTERNAL_DB_ERROR; + } + } + + if (dbwrap_transaction_commit(ctx->db) != 0) { + DEBUG(0, ("Unable to commit upgrade transaction!\n")); + return NT_STATUS_INTERNAL_DB_ERROR; + } + + return NT_STATUS_OK; +} + +static NTSTATUS idmap_tdb_open_db(struct idmap_domain *dom) +{ + NTSTATUS ret; + TALLOC_CTX *mem_ctx; + char *tdbfile = NULL; + struct db_context *db = NULL; + int32_t version; + struct idmap_tdb_common_context *ctx; + + ctx = talloc_get_type(dom->private_data, + struct idmap_tdb_common_context); + + if (ctx->db) { + /* it is already open */ + return NT_STATUS_OK; + } + + /* use our own context here */ + mem_ctx = talloc_stackframe(); + + /* use the old database if present */ + tdbfile = state_path(talloc_tos(), "winbindd_idmap.tdb"); + if (!tdbfile) { + DEBUG(0, ("Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + DEBUG(10,("Opening tdbfile %s\n", tdbfile )); + + /* Open idmap repository */ + db = db_open(mem_ctx, tdbfile, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644, + DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE); + if (!db) { + DEBUG(0, ("Unable to open idmap database\n")); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + /* check against earlier versions */ + ret = dbwrap_fetch_int32_bystring(db, "IDMAP_VERSION", &version); + if (!NT_STATUS_IS_OK(ret)) { + version = -1; + } + + if (version != IDMAP_VERSION) { + if (dbwrap_transaction_start(db) != 0) { + DEBUG(0, ("Unable to start upgrade transaction!\n")); + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + + if (!idmap_tdb_upgrade(dom, db)) { + dbwrap_transaction_cancel(db); + DEBUG(0, ("Unable to open idmap database, it's in an old format, and upgrade failed!\n")); + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + + if (dbwrap_transaction_commit(db) != 0) { + DEBUG(0, ("Unable to commit upgrade transaction!\n")); + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + } + + ctx->db = talloc_move(ctx, &db); + + ret = idmap_tdb_init_hwm(dom); + +done: + talloc_free(mem_ctx); + return ret; +} + +/********************************************************************** + IDMAP MAPPING TDB BACKEND +**********************************************************************/ + +/***************************** + Initialise idmap database. +*****************************/ + +static NTSTATUS idmap_tdb_db_init(struct idmap_domain *dom) +{ + NTSTATUS ret; + struct idmap_tdb_common_context *ctx; + + DEBUG(10, ("idmap_tdb_db_init called for domain '%s'\n", dom->name)); + + ctx = talloc_zero(dom, struct idmap_tdb_common_context); + if ( ! ctx) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + /* load backend specific configuration here: */ +#if 0 + if (strequal(dom->name, "*")) { + } else { + } +#endif + + ctx->rw_ops = talloc_zero(ctx, struct idmap_rw_ops); + if (ctx->rw_ops == NULL) { + DEBUG(0, ("Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto failed; + } + + ctx->max_id = dom->high_id; + ctx->hwmkey_uid = HWM_USER; + ctx->hwmkey_gid = HWM_GROUP; + + ctx->rw_ops->get_new_id = idmap_tdb_common_get_new_id; + ctx->rw_ops->set_mapping = idmap_tdb_common_set_mapping; + + dom->private_data = ctx; + + ret = idmap_tdb_open_db(dom); + if ( ! NT_STATUS_IS_OK(ret)) { + goto failed; + } + + return NT_STATUS_OK; + +failed: + talloc_free(ctx); + return ret; +} + +static const struct idmap_methods db_methods = { + .init = idmap_tdb_db_init, + .unixids_to_sids = idmap_tdb_common_unixids_to_sids, + .sids_to_unixids = idmap_tdb_common_sids_to_unixids, + .allocate_id = idmap_tdb_common_get_new_id, +}; + +NTSTATUS idmap_tdb_init(TALLOC_CTX *mem_ctx) +{ + DEBUG(10, ("calling idmap_tdb_init\n")); + + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "tdb", &db_methods); +} diff --git a/source3/winbindd/idmap_tdb2.c b/source3/winbindd/idmap_tdb2.c new file mode 100644 index 0000000..d843aee --- /dev/null +++ b/source3/winbindd/idmap_tdb2.c @@ -0,0 +1,612 @@ +/* + Unix SMB/CIFS implementation. + + idmap TDB2 backend, used for clustered Samba setups. + + This uses dbwrap to access tdb files. The location can be set + using tdb:idmap2.tdb =" in smb.conf + + Copyright (C) Andrew Tridgell 2007 + + This is heavily based upon idmap_tdb.c, which is: + + Copyright (C) Tim Potter 2000 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Jeremy Allison 2006 + Copyright (C) Simo Sorce 2003-2006 + Copyright (C) Michael Adam 2009-2010 + + 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 "system/filesys.h" +#include "winbindd.h" +#include "idmap.h" +#include "idmap_rw.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "../libcli/security/dom_sid.h" +#include "util_tdb.h" +#include "idmap_tdb_common.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +struct idmap_tdb2_context { + const char *script; /* script to provide idmaps */ +}; + +/* High water mark keys */ +#define HWM_GROUP "GROUP HWM" +#define HWM_USER "USER HWM" + +/* + * check and initialize high/low water marks in the db + */ +static NTSTATUS idmap_tdb2_init_hwm(struct idmap_domain *dom) +{ + NTSTATUS status; + uint32_t low_id; + struct idmap_tdb_common_context *ctx; + + ctx = talloc_get_type(dom->private_data, + struct idmap_tdb_common_context); + + /* Create high water marks for group and user id */ + + status = dbwrap_fetch_uint32_bystring(ctx->db, HWM_USER, &low_id); + if (!NT_STATUS_IS_OK(status) || (low_id < dom->low_id)) { + status = dbwrap_trans_store_uint32_bystring(ctx->db, HWM_USER, + dom->low_id); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Unable to initialise user hwm in idmap " + "database: %s\n", nt_errstr(status))); + return NT_STATUS_INTERNAL_DB_ERROR; + } + } + + status = dbwrap_fetch_uint32_bystring(ctx->db, HWM_GROUP, &low_id); + if (!NT_STATUS_IS_OK(status) || (low_id < dom->low_id)) { + status = dbwrap_trans_store_uint32_bystring(ctx->db, HWM_GROUP, + dom->low_id); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Unable to initialise group hwm in idmap " + "database: %s\n", nt_errstr(status))); + return NT_STATUS_INTERNAL_DB_ERROR; + } + } + + return NT_STATUS_OK; +} + + +/* + open the permanent tdb + */ +static NTSTATUS idmap_tdb2_open_db(struct idmap_domain *dom) +{ + char *db_path; + struct idmap_tdb_common_context *ctx; + + ctx = talloc_get_type(dom->private_data, + struct idmap_tdb_common_context); + + if (ctx->db) { + /* its already open */ + return NT_STATUS_OK; + } + + db_path = talloc_asprintf(NULL, "%s/idmap2.tdb", lp_private_dir()); + NT_STATUS_HAVE_NO_MEMORY(db_path); + + /* Open idmap repository */ + ctx->db = db_open(ctx, db_path, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0644, + DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE); + if (ctx->db == NULL) { + DEBUG(0, ("Unable to open idmap_tdb2 database '%s'\n", + db_path)); + TALLOC_FREE(db_path); + return NT_STATUS_UNSUCCESSFUL; + } + TALLOC_FREE(db_path); + + return idmap_tdb2_init_hwm(dom); +} + +/** + * store a mapping in the database. + */ + +struct idmap_tdb2_set_mapping_context { + const char *ksidstr; + const char *kidstr; +}; + +static NTSTATUS idmap_tdb2_set_mapping_action(struct db_context *db, + void *private_data) +{ + TDB_DATA data; + NTSTATUS ret; + struct idmap_tdb2_set_mapping_context *state; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + + state = (struct idmap_tdb2_set_mapping_context *)private_data; + + DEBUG(10, ("Storing %s <-> %s map\n", state->ksidstr, state->kidstr)); + + /* check whether sid mapping is already present in db */ + ret = dbwrap_fetch_bystring(db, tmp_ctx, state->ksidstr, &data); + if (NT_STATUS_IS_OK(ret)) { + ret = NT_STATUS_OBJECT_NAME_COLLISION; + goto done; + } + + ret = dbwrap_store_bystring(db, state->ksidstr, + string_term_tdb_data(state->kidstr), + TDB_INSERT); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0, ("Error storing SID -> ID: %s\n", nt_errstr(ret))); + goto done; + } + + ret = dbwrap_store_bystring(db, state->kidstr, + string_term_tdb_data(state->ksidstr), + TDB_INSERT); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0, ("Error storing ID -> SID: %s\n", nt_errstr(ret))); + /* try to remove the previous stored SID -> ID map */ + dbwrap_delete_bystring(db, state->ksidstr); + goto done; + } + + DEBUG(10,("Stored %s <-> %s\n", state->ksidstr, state->kidstr)); + +done: + talloc_free(tmp_ctx); + return ret; +} + +static NTSTATUS idmap_tdb2_set_mapping(struct idmap_domain *dom, const struct id_map *map) +{ + struct idmap_tdb2_context *ctx; + NTSTATUS ret; + char *kidstr; + struct dom_sid_buf sid_str; + struct idmap_tdb_common_context *commonctx; + struct idmap_tdb2_set_mapping_context state; + + if (!map || !map->sid) { + return NT_STATUS_INVALID_PARAMETER; + } + + kidstr = NULL; + + /* TODO: should we filter a set_mapping using low/high filters ? */ + + commonctx = talloc_get_type(dom->private_data, + struct idmap_tdb_common_context); + + ctx = talloc_get_type(commonctx->private_data, + struct idmap_tdb2_context); + + switch (map->xid.type) { + + case ID_TYPE_UID: + kidstr = talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id); + break; + + case ID_TYPE_GID: + kidstr = talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id); + break; + + default: + DEBUG(2, ("INVALID unix ID type: 0x02%x\n", map->xid.type)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (kidstr == NULL) { + DEBUG(0, ("ERROR: Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + state.ksidstr = dom_sid_str_buf(map->sid, &sid_str); + state.kidstr = kidstr; + + ret = dbwrap_trans_do(commonctx->db, idmap_tdb2_set_mapping_action, + &state); + +done: + talloc_free(kidstr); + return ret; +} + +/* + run a script to perform a mapping + + The script should the following command lines: + + SIDTOID S-1-xxxx + IDTOSID UID xxxx + IDTOSID GID xxxx + + and should return one of the following as a single line of text + UID:xxxx + GID:xxxx + SID:xxxx + ERR:xxxx + */ +static NTSTATUS idmap_tdb2_script(struct idmap_tdb2_context *ctx, + struct id_map *map, const char *fmt, ...) + PRINTF_ATTRIBUTE(3,4); + +static NTSTATUS idmap_tdb2_script(struct idmap_tdb2_context *ctx, struct id_map *map, + const char *fmt, ...) +{ + va_list ap; + char *cmd; + FILE *p; + char line[64]; + unsigned long v; + + cmd = talloc_asprintf(ctx, "%s ", ctx->script); + NT_STATUS_HAVE_NO_MEMORY(cmd); + + va_start(ap, fmt); + cmd = talloc_vasprintf_append(cmd, fmt, ap); + va_end(ap); + NT_STATUS_HAVE_NO_MEMORY(cmd); + + p = popen(cmd, "r"); + talloc_free(cmd); + if (p == NULL) { + return NT_STATUS_NONE_MAPPED; + } + + if (fgets(line, sizeof(line)-1, p) == NULL) { + pclose(p); + return NT_STATUS_NONE_MAPPED; + } + pclose(p); + + DEBUG(10,("idmap script gave: %s\n", line)); + + if (sscanf(line, "UID:%lu", &v) == 1) { + map->xid.id = v; + map->xid.type = ID_TYPE_UID; + } else if (sscanf(line, "GID:%lu", &v) == 1) { + map->xid.id = v; + map->xid.type = ID_TYPE_GID; + } else if (strncmp(line, "SID:S-", 6) == 0) { + if (!string_to_sid(map->sid, &line[4])) { + DEBUG(0,("Bad SID in '%s' from idmap script %s\n", + line, ctx->script)); + return NT_STATUS_NONE_MAPPED; + } + } else { + DEBUG(0,("Bad reply '%s' from idmap script %s\n", + line, ctx->script)); + return NT_STATUS_NONE_MAPPED; + } + + return NT_STATUS_OK; +} + + + +/* + Single id to sid lookup function. +*/ +static NTSTATUS idmap_tdb2_id_to_sid(struct idmap_domain *dom, struct id_map *map) +{ + NTSTATUS ret; + TDB_DATA data; + char *keystr; + NTSTATUS status; + struct idmap_tdb_common_context *commonctx; + struct idmap_tdb2_context *ctx; + + + if (!dom || !map) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = idmap_tdb2_open_db(dom); + NT_STATUS_NOT_OK_RETURN(status); + + commonctx = talloc_get_type(dom->private_data, + struct idmap_tdb_common_context); + + ctx = talloc_get_type(commonctx->private_data, + struct idmap_tdb2_context); + + /* apply filters before checking */ + if (!idmap_unix_id_is_in_range(map->xid.id, dom)) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n", + map->xid.id, dom->low_id, dom->high_id)); + return NT_STATUS_NONE_MAPPED; + } + + switch (map->xid.type) { + + case ID_TYPE_UID: + keystr = talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id); + break; + + case ID_TYPE_GID: + keystr = talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id); + break; + + default: + DEBUG(2, ("INVALID unix ID type: 0x02%x\n", map->xid.type)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (keystr == NULL) { + DEBUG(0, ("Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + DEBUG(10,("Fetching record %s\n", keystr)); + + /* Check if the mapping exists */ + status = dbwrap_fetch_bystring(commonctx->db, keystr, keystr, &data); + + if (!NT_STATUS_IS_OK(status)) { + struct dom_sid_buf sidstr; + struct idmap_tdb2_set_mapping_context store_state; + + DEBUG(10,("Record %s not found\n", keystr)); + if (ctx->script == NULL) { + ret = NT_STATUS_NONE_MAPPED; + goto done; + } + + ret = idmap_tdb2_script(ctx, map, "IDTOSID %s", keystr); + if (!NT_STATUS_IS_OK(ret)) { + goto done; + } + + store_state.ksidstr = dom_sid_str_buf(map->sid, &sidstr); + store_state.kidstr = keystr; + + ret = dbwrap_trans_do(commonctx->db, + idmap_tdb2_set_mapping_action, + &store_state); + goto done; + } + + if (!string_to_sid(map->sid, (const char *)data.dptr)) { + DEBUG(10,("INVALID SID (%s) in record %s\n", + (const char *)data.dptr, keystr)); + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + + DEBUG(10,("Found record %s -> %s\n", keystr, (const char *)data.dptr)); + ret = NT_STATUS_OK; + +done: + talloc_free(keystr); + return ret; +} + + +/* + Single sid to id lookup function. +*/ +static NTSTATUS idmap_tdb2_sid_to_id(struct idmap_domain *dom, struct id_map *map) +{ + NTSTATUS ret; + TDB_DATA data; + struct dom_sid_buf keystr; + unsigned long rec_id = 0; + struct idmap_tdb_common_context *commonctx; + struct idmap_tdb2_context *ctx; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + + ret = idmap_tdb2_open_db(dom); + NT_STATUS_NOT_OK_RETURN(ret); + + commonctx = talloc_get_type(dom->private_data, + struct idmap_tdb_common_context); + + ctx = talloc_get_type(commonctx->private_data, + struct idmap_tdb2_context); + + dom_sid_str_buf(map->sid, &keystr); + + DEBUG(10, ("Fetching record %s\n", keystr.buf)); + + /* Check if sid is present in database */ + ret = dbwrap_fetch_bystring(commonctx->db, tmp_ctx, keystr.buf, &data); + if (!NT_STATUS_IS_OK(ret)) { + char *idstr; + struct idmap_tdb2_set_mapping_context store_state; + + DBG_DEBUG("Record %s not found\n", keystr.buf); + + if (ctx->script == NULL) { + ret = NT_STATUS_NONE_MAPPED; + goto done; + } + + ret = idmap_tdb2_script(ctx, map, "SIDTOID %s", keystr.buf); + if (!NT_STATUS_IS_OK(ret)) { + goto done; + } + + /* apply filters before returning result */ + if (!idmap_unix_id_is_in_range(map->xid.id, dom)) { + DEBUG(5, ("Script returned id (%u) out of range " + "(%u - %u). Filtered!\n", + map->xid.id, dom->low_id, dom->high_id)); + ret = NT_STATUS_NONE_MAPPED; + goto done; + } + + idstr = talloc_asprintf(tmp_ctx, "%cID %lu", + map->xid.type == ID_TYPE_UID?'U':'G', + (unsigned long)map->xid.id); + if (idstr == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + store_state.ksidstr = keystr.buf; + store_state.kidstr = idstr; + + ret = dbwrap_trans_do(commonctx->db, + idmap_tdb2_set_mapping_action, + &store_state); + goto done; + } + + /* What type of record is this ? */ + if (sscanf((const char *)data.dptr, "UID %lu", &rec_id) == 1) { /* Try a UID record. */ + map->xid.id = rec_id; + map->xid.type = ID_TYPE_UID; + DBG_DEBUG("Found uid record %s -> %s \n", + keystr.buf, + (const char *)data.dptr ); + ret = NT_STATUS_OK; + + } else if (sscanf((const char *)data.dptr, "GID %lu", &rec_id) == 1) { /* Try a GID record. */ + map->xid.id = rec_id; + map->xid.type = ID_TYPE_GID; + DBG_DEBUG("Found gid record %s -> %s \n", + keystr.buf, + (const char *)data.dptr ); + ret = NT_STATUS_OK; + + } else { /* Unknown record type ! */ + DBG_WARNING("Found INVALID record %s -> %s\n", + keystr.buf, + (const char *)data.dptr); + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + + /* apply filters before returning result */ + if (!idmap_unix_id_is_in_range(map->xid.id, dom)) { + DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n", + map->xid.id, dom->low_id, dom->high_id)); + ret = NT_STATUS_NONE_MAPPED; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +/* + Initialise idmap database. +*/ +static NTSTATUS idmap_tdb2_db_init(struct idmap_domain *dom) +{ + NTSTATUS ret; + struct idmap_tdb_common_context *commonctx; + struct idmap_tdb2_context *ctx; + const char * idmap_script = NULL; + const char *ctx_script = NULL; + + commonctx = talloc_zero(dom, struct idmap_tdb_common_context); + if(!commonctx) { + DEBUG(0, ("Out of memory!\n")); + return NT_STATUS_NO_MEMORY; + } + + commonctx->rw_ops = talloc_zero(commonctx, struct idmap_rw_ops); + if (commonctx->rw_ops == NULL) { + DEBUG(0, ("Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto failed; + } + + ctx = talloc_zero(commonctx, struct idmap_tdb2_context); + if (!ctx) { + DEBUG(0, ("Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto failed; + } + + ctx_script = idmap_config_const_string(dom->name, "script", NULL); + + idmap_script = lp_parm_const_string(-1, "idmap", "script", NULL); + if (idmap_script != NULL) { + DEBUG(0, ("Warning: 'idmap:script' is deprecated. " + " Please use 'idmap config * : script' instead!\n")); + } + + if (strequal(dom->name, "*") && ctx_script == NULL) { + /* fall back to idmap:script for backwards compatibility */ + ctx_script = idmap_script; + } + + if (ctx_script) { + DEBUG(1, ("using idmap script '%s'\n", ctx_script)); + /* + * We must ensure this memory is owned by ctx. + * The ctx_script const pointer is a pointer into + * the config file data and may become invalid + * on config file reload. BUG: 13956 + */ + ctx->script = talloc_strdup(ctx, ctx_script); + if (ctx->script == NULL) { + ret = NT_STATUS_NO_MEMORY; + goto failed; + } + } + + commonctx->max_id = dom->high_id; + commonctx->hwmkey_uid = HWM_USER; + commonctx->hwmkey_gid = HWM_GROUP; + + commonctx->sid_to_unixid_fn = idmap_tdb2_sid_to_id; + commonctx->unixid_to_sid_fn = idmap_tdb2_id_to_sid; + + commonctx->rw_ops->get_new_id = idmap_tdb_common_get_new_id; + commonctx->rw_ops->set_mapping = idmap_tdb2_set_mapping; + + commonctx->private_data = ctx; + dom->private_data = commonctx; + + ret = idmap_tdb2_open_db(dom); + if (!NT_STATUS_IS_OK(ret)) { + goto failed; + } + + return NT_STATUS_OK; + +failed: + talloc_free(commonctx); + return ret; +} + + +static const struct idmap_methods db_methods = { + .init = idmap_tdb2_db_init, + .unixids_to_sids = idmap_tdb_common_unixids_to_sids, + .sids_to_unixids = idmap_tdb_common_sids_to_unixids, + .allocate_id = idmap_tdb_common_get_new_id +}; + +static_decl_idmap; +NTSTATUS idmap_tdb2_init(TALLOC_CTX *ctx) +{ + return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "tdb2", &db_methods); +} diff --git a/source3/winbindd/idmap_tdb_common.c b/source3/winbindd/idmap_tdb_common.c new file mode 100644 index 0000000..0df8f2f --- /dev/null +++ b/source3/winbindd/idmap_tdb_common.c @@ -0,0 +1,664 @@ +/* + Unix SMB/CIFS implementation. + + common functions for TDB based idmapping backends + + Copyright (C) Christian Ambach 2012 + + These functions were initially copied over from idmap_tdb.c and idmap_tdb2.c + which are: + + Copyright (C) Tim Potter 2000 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Jeremy Allison 2006 + Copyright (C) Simo Sorce 2003-2006 + Copyright (C) Michael Adam 2009-2010 + Copyright (C) Andrew Tridgell 2007 + + 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_tdb_common.h" +#include "dbwrap/dbwrap.h" +#include "util_tdb.h" +#include "idmap_rw.h" +#include "../libcli/security/dom_sid.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +struct idmap_tdb_common_allocate_id_context { + const char *hwmkey; + const char *hwmtype; + uint32_t high_hwm; + uint32_t hwm; +}; + +static NTSTATUS idmap_tdb_common_allocate_id_action(struct db_context *db, + void *private_data) +{ + NTSTATUS ret; + struct idmap_tdb_common_allocate_id_context *state = private_data; + uint32_t hwm; + + ret = dbwrap_fetch_uint32_bystring(db, state->hwmkey, &hwm); + if (!NT_STATUS_IS_OK(ret)) { + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + + /* check it is in the range */ + if (hwm > state->high_hwm) { + DEBUG(1, ("Fatal Error: %s range full!! (max: %lu)\n", + state->hwmtype, (unsigned long)state->high_hwm)); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + /* fetch a new id and increment it */ + ret = dbwrap_change_uint32_atomic_bystring(db, state->hwmkey, &hwm, 1); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1, ("Fatal error while fetching a new %s value\n!", + state->hwmtype)); + goto done; + } + + /* recheck it is in the range */ + if (hwm > state->high_hwm) { + DEBUG(1, ("Fatal Error: %s range full!! (max: %lu)\n", + state->hwmtype, (unsigned long)state->high_hwm)); + ret = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + ret = NT_STATUS_OK; + state->hwm = hwm; + + done: + return ret; +} + +static NTSTATUS idmap_tdb_common_allocate_id(struct idmap_domain *dom, + struct unixid *xid) +{ + const char *hwmkey; + const char *hwmtype; + uint32_t hwm = 0; + NTSTATUS status; + struct idmap_tdb_common_allocate_id_context state; + struct idmap_tdb_common_context *ctx; + + ctx = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + /* Get current high water mark */ + switch (xid->type) { + + case ID_TYPE_UID: + hwmkey = ctx->hwmkey_uid; + hwmtype = "UID"; + break; + + case ID_TYPE_GID: + hwmkey = ctx->hwmkey_gid; + hwmtype = "GID"; + break; + + case ID_TYPE_BOTH: + /* + * This is not supported here yet and + * already handled in idmap_rw_new_mapping() + */ + FALL_THROUGH; + case ID_TYPE_NOT_SPECIFIED: + /* + * This is handled in idmap_rw_new_mapping() + */ + FALL_THROUGH; + default: + DEBUG(2, ("Invalid ID type (0x%x)\n", xid->type)); + return NT_STATUS_INVALID_PARAMETER; + } + + state.hwm = hwm; + state.high_hwm = ctx->max_id; + state.hwmtype = hwmtype; + state.hwmkey = hwmkey; + + status = dbwrap_trans_do(ctx->db, idmap_tdb_common_allocate_id_action, + &state); + + if (NT_STATUS_IS_OK(status)) { + xid->id = state.hwm; + DEBUG(10, ("New %s = %d\n", hwmtype, state.hwm)); + } else { + DEBUG(1, ("Error allocating a new %s\n", hwmtype)); + } + + return status; +} + +/** + * Allocate a new unix-ID. + * For now this is for the default idmap domain only. + * Should be extended later on. + */ +NTSTATUS idmap_tdb_common_get_new_id(struct idmap_domain * dom, + struct unixid * id) +{ + NTSTATUS ret; + + if (!strequal(dom->name, "*")) { + DEBUG(3, ("idmap_tdb_common_get_new_id: " + "Refusing allocation of a new unixid for domain'%s'. " + "Currently only supported for the default " + "domain \"*\".\n", dom->name)); + return NT_STATUS_NOT_IMPLEMENTED; + } + + ret = idmap_tdb_common_allocate_id(dom, id); + + return ret; +} + +/** + * store a mapping in the database. + */ + +struct idmap_tdb_common_set_mapping_context { + const char *ksidstr; + const char *kidstr; +}; + +static NTSTATUS idmap_tdb_common_set_mapping_action(struct db_context *db, + void *private_data) +{ + TDB_DATA data; + NTSTATUS ret; + struct idmap_tdb_common_set_mapping_context *state = private_data; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + + DEBUG(10, ("Storing %s <-> %s map\n", state->ksidstr, state->kidstr)); + + /* check whether sid mapping is already present in db */ + ret = dbwrap_fetch_bystring(db, tmp_ctx, state->ksidstr, &data); + if (NT_STATUS_IS_OK(ret)) { + ret = NT_STATUS_OBJECT_NAME_COLLISION; + goto done; + } + + ret = dbwrap_store_bystring(db, state->ksidstr, + string_term_tdb_data(state->kidstr), + TDB_INSERT); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0, ("Error storing SID -> ID: %s\n", nt_errstr(ret))); + goto done; + } + + ret = dbwrap_store_bystring(db, state->kidstr, + string_term_tdb_data(state->ksidstr), + TDB_INSERT); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0, ("Error storing ID -> SID: %s\n", nt_errstr(ret))); + /* try to remove the previous stored SID -> ID map */ + dbwrap_delete_bystring(db, state->ksidstr); + goto done; + } + + DEBUG(10, ("Stored %s <-> %s\n", state->ksidstr, state->kidstr)); + + done: + talloc_free(tmp_ctx); + return ret; +} + +NTSTATUS idmap_tdb_common_set_mapping(struct idmap_domain * dom, + const struct id_map * map) +{ + struct idmap_tdb_common_context *ctx; + struct idmap_tdb_common_set_mapping_context state; + NTSTATUS ret; + struct dom_sid_buf ksidstr; + char *kidstr = NULL; + + if (!map || !map->sid) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* TODO: should we filter a set_mapping using low/high filters ? */ + + ctx = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + switch (map->xid.type) { + + case ID_TYPE_UID: + kidstr = + talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id); + break; + + case ID_TYPE_GID: + kidstr = + talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id); + break; + + default: + DEBUG(2, ("INVALID unix ID type: 0x%02x\n", map->xid.type)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (kidstr == NULL) { + DEBUG(0, ("ERROR: Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + state.ksidstr = dom_sid_str_buf(map->sid, &ksidstr); + state.kidstr = kidstr; + + ret = dbwrap_trans_do(ctx->db, idmap_tdb_common_set_mapping_action, + &state); + + done: + talloc_free(kidstr); + return ret; +} + +/* + * Create a new mapping for an unmapped SID, also allocating a new ID. + * This should be run inside a transaction. + * + * TODO: + * Properly integrate this with multi domain idmap config: + * Currently, the allocator is default-config only. + */ +NTSTATUS idmap_tdb_common_new_mapping(struct idmap_domain * dom, + struct id_map * map) +{ + NTSTATUS ret; + struct idmap_tdb_common_context *ctx; + + ctx = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + ret = idmap_rw_new_mapping(dom, ctx->rw_ops, map); + + return ret; +} + +/* + lookup a set of unix ids +*/ +NTSTATUS idmap_tdb_common_unixids_to_sids(struct idmap_domain * dom, + struct id_map ** ids) +{ + NTSTATUS ret; + size_t i, num_mapped = 0; + struct idmap_tdb_common_context *ctx; + + NTSTATUS(*unixid_to_sid_fn) (struct idmap_domain * dom, + struct id_map * map); + ctx = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + if (ctx->unixid_to_sid_fn == NULL) { + unixid_to_sid_fn = idmap_tdb_common_unixid_to_sid; + } else { + unixid_to_sid_fn = ctx->unixid_to_sid_fn; + } + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + + for (i = 0; ids[i]; i++) { + ret = unixid_to_sid_fn(dom, ids[i]); + if (!NT_STATUS_IS_OK(ret)) { + + /* if it is just a failed mapping continue */ + if (NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) { + + /* make sure it is marked as unmapped */ + ids[i]->status = ID_UNMAPPED; + continue; + } + + /* some fatal error occurred, return immediately */ + goto done; + } + + /* all ok, id is mapped */ + ids[i]->status = ID_MAPPED; + num_mapped += 1; + } + + ret = NT_STATUS_OK; + +done: + + if (NT_STATUS_IS_OK(ret)) { + if (i == 0 || num_mapped == 0) { + ret = NT_STATUS_NONE_MAPPED; + } else if (num_mapped < i) { + ret = STATUS_SOME_UNMAPPED; + } else { + ret = NT_STATUS_OK; + } + } + + return ret; +} + +/* + default single id to sid lookup function +*/ +NTSTATUS idmap_tdb_common_unixid_to_sid(struct idmap_domain * dom, + struct id_map * map) +{ + NTSTATUS ret; + TDB_DATA data; + char *keystr; + struct idmap_tdb_common_context *ctx; + + if (!dom || !map) { + return NT_STATUS_INVALID_PARAMETER; + } + + ctx = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + /* apply filters before checking */ + if (!idmap_unix_id_is_in_range(map->xid.id, dom)) { + DEBUG(5, + ("Requested id (%u) out of range (%u - %u). Filtered!\n", + map->xid.id, dom->low_id, dom->high_id)); + return NT_STATUS_NONE_MAPPED; + } + + switch (map->xid.type) { + + case ID_TYPE_UID: + keystr = + talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id); + break; + + case ID_TYPE_GID: + keystr = + talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id); + break; + + default: + DEBUG(2, ("INVALID unix ID type: 0x%02x\n", map->xid.type)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (keystr == NULL) { + DEBUG(0, ("Out of memory!\n")); + ret = NT_STATUS_NO_MEMORY; + goto done; + } + + DEBUG(10, ("Fetching record %s\n", keystr)); + + /* Check if the mapping exists */ + ret = dbwrap_fetch_bystring(ctx->db, keystr, keystr, &data); + + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(10, ("Record %s not found\n", keystr)); + ret = NT_STATUS_NONE_MAPPED; + goto done; + } + + if ((data.dsize == 0) || (data.dptr[data.dsize-1] != '\0')) { + DBG_DEBUG("Invalid record length %zu\n", data.dsize); + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + + if (!string_to_sid(map->sid, (const char *)data.dptr)) { + DEBUG(10, ("INVALID SID (%s) in record %s\n", + (const char *)data.dptr, keystr)); + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + + DEBUG(10, ("Found record %s -> %s\n", keystr, (const char *)data.dptr)); + ret = NT_STATUS_OK; + + done: + talloc_free(keystr); + return ret; +} + +/********************************** + Single sid to id lookup function. +**********************************/ + +NTSTATUS idmap_tdb_common_sid_to_unixid(struct idmap_domain * dom, + struct id_map * map) +{ + NTSTATUS ret; + TDB_DATA data; + struct dom_sid_buf keystr; + unsigned long rec_id = 0; + struct idmap_tdb_common_context *ctx; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + + if (!dom || !map) { + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + ctx = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + dom_sid_str_buf(map->sid, &keystr); + + DEBUG(10, ("Fetching record %s\n", keystr.buf)); + + /* Check if sid is present in database */ + ret = dbwrap_fetch_bystring(ctx->db, tmp_ctx, keystr.buf, &data); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(10, ("Record %s not found\n", keystr.buf)); + ret = NT_STATUS_NONE_MAPPED; + goto done; + } + + /* What type of record is this ? */ + if (sscanf((const char *)data.dptr, "UID %lu", &rec_id) == 1) { + /* Try a UID record. */ + map->xid.id = rec_id; + map->xid.type = ID_TYPE_UID; + DEBUG(10, + ("Found uid record %s -> %s \n", keystr.buf, + (const char *)data.dptr)); + ret = NT_STATUS_OK; + + } else if (sscanf((const char *)data.dptr, "GID %lu", &rec_id) == 1) { + /* Try a GID record. */ + map->xid.id = rec_id; + map->xid.type = ID_TYPE_GID; + DEBUG(10, + ("Found gid record %s -> %s \n", keystr.buf, + (const char *)data.dptr)); + ret = NT_STATUS_OK; + + } else { /* Unknown record type ! */ + DEBUG(2, + ("Found INVALID record %s -> %s\n", keystr.buf, + (const char *)data.dptr)); + ret = NT_STATUS_INTERNAL_DB_ERROR; + goto done; + } + + /* apply filters before returning result */ + if (!idmap_unix_id_is_in_range(map->xid.id, dom)) { + DEBUG(5, + ("Requested id (%u) out of range (%u - %u). Filtered!\n", + map->xid.id, dom->low_id, dom->high_id)); + ret = NT_STATUS_NONE_MAPPED; + } + + done: + talloc_free(tmp_ctx); + return ret; +} + +/********************************** + lookup a set of sids +**********************************/ + +struct idmap_tdb_common_sids_to_unixids_context { + struct idmap_domain *dom; + struct id_map **ids; + bool allocate_unmapped; + NTSTATUS(*sid_to_unixid_fn) (struct idmap_domain * dom, + struct id_map * map); +}; + +static NTSTATUS idmap_tdb_common_sids_to_unixids_action(struct db_context *db, + void *private_data) +{ + struct idmap_tdb_common_sids_to_unixids_context *state = private_data; + size_t i, num_mapped = 0, num_required = 0; + NTSTATUS ret = NT_STATUS_OK; + + DEBUG(10, ("idmap_tdb_common_sids_to_unixids: " + " domain: [%s], allocate: %s\n", + state->dom->name, state->allocate_unmapped ? "yes" : "no")); + + for (i = 0; state->ids[i]; i++) { + if ((state->ids[i]->status == ID_UNKNOWN) || + /* retry if we could not map in previous run: */ + (state->ids[i]->status == ID_UNMAPPED)) { + NTSTATUS ret2; + + ret2 = state->sid_to_unixid_fn(state->dom, + state->ids[i]); + + if (!NT_STATUS_IS_OK(ret2)) { + + /* if it is just a failed mapping, continue */ + if (NT_STATUS_EQUAL + (ret2, NT_STATUS_NONE_MAPPED)) { + + /* make sure it is marked as unmapped */ + state->ids[i]->status = ID_UNMAPPED; + ret = STATUS_SOME_UNMAPPED; + } else { + /* + * some fatal error occurred, + * return immediately + */ + ret = ret2; + goto done; + } + } else { + /* all ok, id is mapped */ + state->ids[i]->status = ID_MAPPED; + } + } + + if (state->ids[i]->status == ID_MAPPED) { + num_mapped += 1; + } + + if ((state->ids[i]->status == ID_UNMAPPED) && + state->allocate_unmapped) { + ret = + idmap_tdb_common_new_mapping(state->dom, + state->ids[i]); + DBG_DEBUG("idmap_tdb_common_new_mapping returned %s\n", + nt_errstr(ret)); + if (NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED)) { + if (state->ids[i]->status == ID_REQUIRE_TYPE) { + num_required += 1; + continue; + } + } + if (!NT_STATUS_IS_OK(ret)) { + ret = STATUS_SOME_UNMAPPED; + continue; + } + num_mapped += 1; + } + } + +done: + + if (NT_STATUS_IS_OK(ret) || + NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED)) { + if (i == 0 || num_mapped == 0) { + ret = NT_STATUS_NONE_MAPPED; + } else if (num_mapped < i) { + ret = STATUS_SOME_UNMAPPED; + } else { + ret = NT_STATUS_OK; + } + if (num_required > 0) { + ret = STATUS_SOME_UNMAPPED; + } + } + + return ret; +} + +NTSTATUS idmap_tdb_common_sids_to_unixids(struct idmap_domain * dom, + struct id_map ** ids) +{ + NTSTATUS ret; + int i; + struct idmap_tdb_common_sids_to_unixids_context state; + struct idmap_tdb_common_context *ctx; + + ctx = + talloc_get_type_abort(dom->private_data, + struct idmap_tdb_common_context); + + /* initialize the status to avoid surprise */ + for (i = 0; ids[i]; i++) { + ids[i]->status = ID_UNKNOWN; + } + + state.dom = dom; + state.ids = ids; + state.allocate_unmapped = false; + if (ctx->sid_to_unixid_fn == NULL) { + state.sid_to_unixid_fn = idmap_tdb_common_sid_to_unixid; + } else { + state.sid_to_unixid_fn = ctx->sid_to_unixid_fn; + } + + ret = idmap_tdb_common_sids_to_unixids_action(ctx->db, &state); + + if ( (NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED) || + NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) && + !dom->read_only) { + state.allocate_unmapped = true; + ret = dbwrap_trans_do(ctx->db, + idmap_tdb_common_sids_to_unixids_action, + &state); + } + + return ret; +} diff --git a/source3/winbindd/idmap_tdb_common.h b/source3/winbindd/idmap_tdb_common.h new file mode 100644 index 0000000..3343b58 --- /dev/null +++ b/source3/winbindd/idmap_tdb_common.h @@ -0,0 +1,137 @@ +/* + Unix SMB/CIFS implementation. + + common functions for TDB based idmapping backends + + Copyright (C) Christian Ambach 2012 + + These functions were initially copied over from idmap_tdb.c and idmap_tdb2.c + which are: + + Copyright (C) Tim Potter 2000 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Jeremy Allison 2006 + Copyright (C) Simo Sorce 2003-2006 + Copyright (C) Michael Adam 2009-2010 + Copyright (C) Andrew Tridgell 2007 + + 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_TDB_COMMON_H_ +#define _IDMAP_TDB_COMMON_H_ + +#include "includes.h" +#include "idmap.h" +#include "dbwrap/dbwrap.h" + +/* + * this must be stored in idmap_domain->private_data + * when using idmap_tdb_common_get_new_id and the + * mapping functions idmap_tdb_common_unixid(s)_to_sids + * + * private_data can be used for backend specific + * configuration data (e.g. idmap script in idmap_tdb2) + * + */ +struct idmap_tdb_common_context { + struct db_context *db; + struct idmap_rw_ops *rw_ops; + /* + * what is the maximum xid to be allocated + * this is typically just dom->high_id + */ + uint32_t max_id; + const char *hwmkey_uid; + const char *hwmkey_gid; + /** + * if not set, idmap_tdb_common_unixids_to_sid will be used by + * idmap_tdb_common_unixids_to_sids + */ + NTSTATUS(*unixid_to_sid_fn) (struct idmap_domain *dom, + struct id_map * map); + /* + * if not set, idmap_tdb_common_sid_to_id will be used by + * idmap_tdb_common_sids_to_unixids + */ + NTSTATUS(*sid_to_unixid_fn) (struct idmap_domain *dom, + struct id_map * map); + void *private_data; +}; + +/** + * Allocate a new unix-ID. + * For now this is for the default idmap domain only. + * Should be extended later on. + */ +NTSTATUS idmap_tdb_common_get_new_id(struct idmap_domain *dom, + struct unixid *id); + +/* + * store a mapping into the idmap database + * + * the entries that will be stored are + * UID map->xid.id => map->sid and map->sid => UID map->xid.id + * or + * GID map->xid.id => map->sid and map->sid => GID map->xid.id + * + * for example + * UID 12345 = S-1-5-21-297746067-1479432880-4056370663 + * S-1-5-21-297746067-1479432880-4056370663 = UID 12345 + * + */ +NTSTATUS idmap_tdb_common_set_mapping(struct idmap_domain *dom, + const struct id_map *map); + +/* + * Create a new mapping for an unmapped SID, also allocating a new ID. + * This should be run inside a transaction. + * + * TODO: + * Properly integrate this with multi domain idmap config: + * Currently, the allocator is default-config only. + */ +NTSTATUS idmap_tdb_common_new_mapping(struct idmap_domain *dom, + struct id_map *map); + +/* + * default multiple id to sid lookup function + * + * will call idmap_tdb_common_unixid_to_sid for each mapping + * if no other function to lookup unixid_to_sid was given in + * idmap_tdb_common_context + */ +NTSTATUS idmap_tdb_common_unixids_to_sids(struct idmap_domain *dom, + struct id_map **ids); + +/* + * default single id to sid lookup function + * + * will read the entries written by idmap_tdb_common_set_mapping + */ +NTSTATUS idmap_tdb_common_unixid_to_sid(struct idmap_domain *dom, + struct id_map *map); + +/********************************** + Single sid to id lookup function. +**********************************/ + +NTSTATUS idmap_tdb_common_sid_to_unixid(struct idmap_domain *dom, + struct id_map *map); + +NTSTATUS idmap_tdb_common_sids_to_unixids(struct idmap_domain *dom, + struct id_map **ids); + +#endif /* _IDMAP_TDB_COMMON_H_ */ diff --git a/source3/winbindd/idmap_util.c b/source3/winbindd/idmap_util.c new file mode 100644 index 0000000..fd2ae4a --- /dev/null +++ b/source3/winbindd/idmap_util.c @@ -0,0 +1,137 @@ +/* + Unix SMB/CIFS implementation. + ID Mapping + Copyright (C) Simo Sorce 2003 + Copyright (C) Jeremy Allison 2006 + Copyright (C) Michael Adam 2010 + + 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.h" +#include "winbindd_proto.h" +#include "idmap.h" +#include "idmap_cache.h" +#include "../libcli/security/security.h" +#include "secrets.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_IDMAP + +/** + * check whether a given unix id is inside the filter range of an idmap domain + */ +bool idmap_unix_id_is_in_range(uint32_t id, struct idmap_domain *dom) +{ + if ((dom->low_id && (id < dom->low_id)) || + (dom->high_id && (id > dom->high_id))) + { + return false; + } + + return true; +} + +/** + * Helper for unixids_to_sids: find entry by id in mapping array, + * search up to IDMAP_AD_MAX_IDS entries + */ +struct id_map *idmap_find_map_by_id(struct id_map **maps, enum id_type type, + uint32_t id) +{ + int i; + + for (i = 0; maps[i] != NULL; i++) { + if ((maps[i]->xid.type == type) && (maps[i]->xid.id == id)) { + return maps[i]; + } + } + + return NULL; +} + +/** + * Helper for sids_to_unix_ids: find entry by SID in mapping array, + * search up to IDMAP_AD_MAX_IDS entries + */ +struct id_map *idmap_find_map_by_sid(struct id_map **maps, struct dom_sid *sid) +{ + int i; + + for (i = 0; i < IDMAP_LDAP_MAX_IDS; i++) { + if (maps[i] == NULL) { /* end of the run */ + return NULL; + } + if (dom_sid_equal(maps[i]->sid, sid)) { + return maps[i]; + } + } + + return NULL; +} + +char *idmap_fetch_secret(const char *backend, const char *domain, + const char *identity) +{ + char *tmp, *ret; + int r; + + r = asprintf(&tmp, "IDMAP_%s_%s", backend, domain); + + if (r < 0) + return NULL; + + /* make sure the key is case insensitive */ + if (!strupper_m(tmp)) { + SAFE_FREE(tmp); + return NULL; + } + + ret = secrets_fetch_generic(tmp, identity); + + SAFE_FREE(tmp); + + return ret; +} + +struct id_map **id_map_ptrs_init(TALLOC_CTX *mem_ctx, size_t num_ids) +{ + struct id_map **ptrs; + struct id_map *maps; + struct dom_sid *sids; + size_t i; + + ptrs = talloc_array(mem_ctx, struct id_map *, num_ids+1); + if (ptrs == NULL) { + return NULL; + } + maps = talloc_array(ptrs, struct id_map, num_ids); + if (maps == NULL) { + TALLOC_FREE(ptrs); + return NULL; + } + sids = talloc_zero_array(ptrs, struct dom_sid, num_ids); + if (sids == NULL) { + TALLOC_FREE(ptrs); + return NULL; + } + + for (i=0; i<num_ids; i++) { + maps[i] = (struct id_map) { .sid = &sids[i] }; + ptrs[i] = &maps[i]; + } + ptrs[num_ids] = NULL; + + return ptrs; +} diff --git a/source3/winbindd/nss_info.c b/source3/winbindd/nss_info.c new file mode 100644 index 0000000..9c502e8 --- /dev/null +++ b/source3/winbindd/nss_info.c @@ -0,0 +1,377 @@ +/* + Unix SMB/CIFS implementation. + Idmap NSS headers + + Copyright (C) Gerald Carter 2006 + Copyright (C) Michael Adam 2008 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "nss_info.h" + +static struct nss_function_entry *backends = NULL; +static struct nss_function_entry *default_backend = NULL; +static struct nss_domain_entry *nss_domain_list = NULL; + +/********************************************************************** + Get idmap nss methods. +**********************************************************************/ + +static struct nss_function_entry *nss_get_backend(const char *name ) +{ + struct nss_function_entry *entry = backends; + + for(entry = backends; entry; entry = entry->next) { + if ( strequal(entry->name, name) ) + return entry; + } + + return NULL; +} + +/********************************************************************* + Allow a module to register itself as a backend. +**********************************************************************/ + + NTSTATUS smb_register_idmap_nss(int version, const char *name, + const struct nss_info_methods *methods) +{ + struct nss_function_entry *entry; + + if ((version != SMB_NSS_INFO_INTERFACE_VERSION)) { + DEBUG(0, ("smb_register_idmap_nss: Failed to register idmap_nss module.\n" + "The module was compiled against SMB_NSS_INFO_INTERFACE_VERSION %d,\n" + "current SMB_NSS_INFO_INTERFACE_VERSION is %d.\n" + "Please recompile against the current version of samba!\n", + version, SMB_NSS_INFO_INTERFACE_VERSION)); + return NT_STATUS_OBJECT_TYPE_MISMATCH; + } + + if (!name || !name[0] || !methods) { + DEBUG(0,("smb_register_idmap_nss: called with NULL pointer or empty name!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if ( nss_get_backend(name) ) { + DEBUG(5,("smb_register_idmap_nss: idmap module %s " + "already registered!\n", name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + entry = SMB_XMALLOC_P(struct nss_function_entry); + entry->name = smb_xstrdup(name); + entry->methods = methods; + + DLIST_ADD(backends, entry); + DEBUG(5, ("smb_register_idmap_nss: Successfully added idmap " + "nss backend '%s'\n", name)); + + return NT_STATUS_OK; +} + +/******************************************************************** + *******************************************************************/ + +static bool parse_nss_parm(TALLOC_CTX *mem_ctx, + const char *config, + char **backend, + char **domain) +{ + char *p; + + *backend = *domain = NULL; + + if ( !config ) + return False; + + p = strchr( config, ':' ); + + /* if no : then the string must be the backend name only */ + + if ( !p ) { + *backend = talloc_strdup(mem_ctx, config); + return (*backend != NULL); + } + + /* split the string and return the two parts */ + + if ( strlen(p+1) > 0 ) { + *domain = talloc_strdup(mem_ctx, p + 1); + } + + *backend = talloc_strndup(mem_ctx, config, PTR_DIFF(p, config)); + return (*backend != NULL); +} + +static NTSTATUS nss_domain_list_add_domain(const char *domain, + struct nss_function_entry *nss_backend) +{ + struct nss_domain_entry *nss_domain; + + nss_domain = talloc_zero(nss_domain_list, struct nss_domain_entry); + if (!nss_domain) { + DEBUG(0, ("nss_domain_list_add_domain: talloc() failure!\n")); + return NT_STATUS_NO_MEMORY; + } + + nss_domain->backend = nss_backend; + if (domain) { + nss_domain->domain = talloc_strdup(nss_domain, domain); + if (!nss_domain->domain) { + DEBUG(0, ("nss_domain_list_add_domain: talloc() " + "failure!\n")); + TALLOC_FREE(nss_domain); + return NT_STATUS_NO_MEMORY; + } + } + + nss_domain->init_status = nss_domain->backend->methods->init(nss_domain); + if (!NT_STATUS_IS_OK(nss_domain->init_status)) { + DEBUG(0, ("nss_init: Failed to init backend '%s' for domain " + "'%s'!\n", nss_backend->name, nss_domain->domain)); + } + + DLIST_ADD(nss_domain_list, nss_domain); + + DEBUG(10, ("Added domain '%s' with backend '%s' to nss_domain_list.\n", + domain, nss_backend->name)); + + return NT_STATUS_OK; +} + +/******************************************************************** + Each nss backend must not store global state, but rather be able + to initialize the state on a per domain basis. + *******************************************************************/ + +static NTSTATUS nss_init(const char **nss_list) +{ + NTSTATUS status; + static bool nss_initialized = false; + int i; + char *backend = NULL, *domain = NULL; + struct nss_function_entry *nss_backend; + TALLOC_CTX *frame; + + /* check for previous successful initializations */ + + if (nss_initialized) { + return NT_STATUS_OK; + } + + frame = talloc_stackframe(); + + /* The "template" backend should always be registered as it + is a static module */ + + nss_backend = nss_get_backend("template"); + if (nss_backend == NULL) { + static_init_nss_info(NULL); + } + + /* Create the list of nss_domains (loading any shared plugins + as necessary) */ + + for ( i=0; nss_list && nss_list[i]; i++ ) { + bool ok; + + ok = parse_nss_parm(frame, nss_list[i], &backend, &domain); + if (!ok) { + DEBUG(0,("nss_init: failed to parse \"%s\"!\n", + nss_list[i])); + continue; + } + + DEBUG(10, ("parsed backend = '%s', domain = '%s'\n", + backend, domain)); + + /* validate the backend */ + + nss_backend = nss_get_backend(backend); + if (nss_backend == NULL) { + /* + * This is a freaking hack. We don't have proper + * modules for nss_info backends. Right now we have + * our standard nss_info backends in the ad backend. + */ + status = smb_probe_module("idmap", "ad"); + if ( !NT_STATUS_IS_OK(status) ) { + continue; + } + } + + nss_backend = nss_get_backend(backend); + if (nss_backend == NULL) { + /* attempt to register the backend */ + status = smb_probe_module( "nss_info", backend ); + if ( !NT_STATUS_IS_OK(status) ) { + continue; + } + } + + /* try again */ + nss_backend = nss_get_backend(backend); + if (nss_backend == NULL) { + DEBUG(0, ("nss_init: unregistered backend %s!. " + "Skipping\n", backend)); + continue; + } + + /* + * The first config item of the list without an explicit domain + * is treated as the default nss info backend. + */ + if ((domain == NULL) && (default_backend == NULL)) { + DEBUG(10, ("nss_init: using '%s' as default backend.\n", + backend)); + default_backend = nss_backend; + } + + status = nss_domain_list_add_domain(domain, nss_backend); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* cleanup */ + + TALLOC_FREE(domain); + TALLOC_FREE(backend); + } + + + if ( !nss_domain_list ) { + DEBUG(3,("nss_init: no nss backends configured. " + "Defaulting to \"template\".\n")); + + + /* we should default to use template here */ + } + + nss_initialized = true; + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +/******************************************************************** + *******************************************************************/ + +static struct nss_domain_entry *find_nss_domain( const char *domain ) +{ + NTSTATUS status; + struct nss_domain_entry *p; + + status = nss_init( lp_winbind_nss_info() ); + if ( !NT_STATUS_IS_OK(status) ) { + DEBUG(4,("find_nss_domain: Failed to init nss_info API " + "(%s)!\n", nt_errstr(status))); + return NULL; + } + + for ( p=nss_domain_list; p; p=p->next ) { + if ( strequal( p->domain, domain ) ) + break; + } + + /* If we didn't find a match, then use the default nss backend */ + + if ( !p ) { + if (!default_backend) { + return NULL; + } + + status = nss_domain_list_add_domain(domain, default_backend); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + /* + * HACK ALERT: + * Here, we use the fact that the new domain was added at + * the beginning of the list... + */ + p = nss_domain_list; + } + + if ( !NT_STATUS_IS_OK( p->init_status ) ) { + p->init_status = p->backend->methods->init( p ); + } + + return p; +} + +/******************************************************************** + *******************************************************************/ + + NTSTATUS nss_map_to_alias( TALLOC_CTX *mem_ctx, const char *domain, + const char *name, char **alias ) +{ + struct nss_domain_entry *p; + const 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, p, name, alias); +} + + +/******************************************************************** + *******************************************************************/ + + NTSTATUS nss_map_from_alias( TALLOC_CTX *mem_ctx, const char *domain, + const char *alias, char **name ) +{ + struct nss_domain_entry *p; + const 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, p, alias, name ); +} + +/******************************************************************** + *******************************************************************/ + + NTSTATUS nss_close( const char *parameters ) +{ + struct nss_domain_entry *p = nss_domain_list; + struct nss_domain_entry *q; + + while ( p && p->backend && p->backend->methods ) { + /* close the backend */ + p->backend->methods->close_fn(); + + /* free the memory */ + q = p; + p = p->next; + TALLOC_FREE( q ); + } + + return NT_STATUS_OK; +} + diff --git a/source3/winbindd/nss_info_template.c b/source3/winbindd/nss_info_template.c new file mode 100644 index 0000000..c58a7fc --- /dev/null +++ b/source3/winbindd/nss_info_template.c @@ -0,0 +1,80 @@ +/* + Unix SMB/CIFS implementation. + idMap nss template plugin + + Copyright (C) Gerald Carter 2006 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "ads.h" +#include "nss_info.h" + +/************************************************************************ + ***********************************************************************/ + +static NTSTATUS nss_template_init( struct nss_domain_entry *e ) +{ + return NT_STATUS_OK; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_template_map_to_alias( TALLOC_CTX *mem_ctx, + struct nss_domain_entry *e, + const char *name, + char **alias ) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/********************************************************************** + *********************************************************************/ + +static NTSTATUS nss_template_map_from_alias( TALLOC_CTX *mem_ctx, + struct nss_domain_entry *e, + const char *alias, + char **name ) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/************************************************************************ + ***********************************************************************/ + +static NTSTATUS nss_template_close( void ) +{ + return NT_STATUS_OK; +} + + +/************************************************************************ + ***********************************************************************/ + +static struct nss_info_methods nss_template_methods = { + .init = nss_template_init, + .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(TALLOC_CTX *mem_ctx) +{ + return smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION, + "template", + &nss_template_methods); +} + diff --git a/source3/winbindd/wb_alias_members.c b/source3/winbindd/wb_alias_members.c new file mode 100644 index 0000000..06c2292 --- /dev/null +++ b/source3/winbindd/wb_alias_members.c @@ -0,0 +1,137 @@ +/* + Unix SMB/CIFS implementation. + async alias_members + Copyright (C) Pavel Filipenský 2023 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" + +struct wb_alias_members_state { + struct tevent_context *ev; + struct dom_sid sid; + struct wbint_SidArray sids; +}; + +static void wb_alias_members_done(struct tevent_req *subreq); + +struct tevent_req *wb_alias_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid, + enum lsa_SidType type, + int max_nesting) +{ + struct tevent_req *req, *subreq; + struct wb_alias_members_state *state; + struct winbindd_domain *domain; + NTSTATUS status; + struct dom_sid_buf buf; + + req = tevent_req_create(mem_ctx, &state, struct wb_alias_members_state); + if (req == NULL) { + return NULL; + } + D_INFO("WB command alias_members start.\nLooking up SID %s.\n", + dom_sid_str_buf(sid, &buf)); + + if (max_nesting <= 0) { + D_DEBUG("Finished. The depth based on 'winbind expand groups' is %d.\n", max_nesting); + state->sids.num_sids = 0; + state->sids.sids = NULL; + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + sid_copy(&state->sid, sid); + + status = lookup_usergroups_cached(state, + &state->sid, + &state->sids.num_sids, + &state->sids.sids); + if (NT_STATUS_IS_OK(status)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + domain = find_domain_from_sid_noinit(&state->sid); + if (domain == NULL) { + DBG_WARNING("could not find domain entry for sid %s\n", + dom_sid_str_buf(&state->sid, &buf)); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_ALIAS); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_LookupAliasMembers_send(state, + ev, + dom_child_handle(domain), + &state->sid, + type, + &state->sids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_alias_members_done, req); + return req; +} + +static void wb_alias_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct wb_alias_members_state *state = + tevent_req_data(req, struct wb_alias_members_state); + NTSTATUS status, result; + + status = dcerpc_wbint_LookupAliasMembers_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_alias_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint32_t *num_sids, + struct dom_sid **sids) +{ + struct wb_alias_members_state *state = + tevent_req_data(req, struct wb_alias_members_state); + NTSTATUS status; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *num_sids = state->sids.num_sids; + *sids = talloc_move(mem_ctx, &state->sids.sids); + + D_INFO("WB command alias_members end.\nReceived %" PRIu32 " SID(s).\n", + *num_sids); + if (CHECK_DEBUGLVL(DBGLVL_INFO)) { + for (i = 0; i < *num_sids; i++) { + struct dom_sid_buf buf; + D_INFO("%" PRIu32 ": %s\n", + i, + dom_sid_str_buf(&(*sids)[i], &buf)); + } + } + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_dsgetdcname.c b/source3/winbindd/wb_dsgetdcname.c new file mode 100644 index 0000000..0f6acaa --- /dev/null +++ b/source3/winbindd/wb_dsgetdcname.c @@ -0,0 +1,255 @@ +/* + Unix SMB/CIFS implementation. + async dsgetdcname + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "lib/gencache.h" + +struct wb_dsgetdcname_state { + const char *domain_name; + struct GUID domain_guid; + struct netr_DsRGetDCNameInfo *dcinfo; +}; + +static void wb_dsgetdcname_done(struct tevent_req *subreq); + +struct tevent_req *wb_dsgetdcname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *domain_name, + const struct GUID *domain_guid, + const char *site_name, + uint32_t flags) +{ + struct tevent_req *req, *subreq; + struct wb_dsgetdcname_state *state; + struct dcerpc_binding_handle *child_binding_handle = NULL; + struct GUID *guid_ptr = NULL; + + req = tevent_req_create(mem_ctx, &state, struct wb_dsgetdcname_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command dsgetdcname start.\n" + "Search domain name %s and site name %s.\n", + domain_name, + site_name); + if (strequal(domain_name, "BUILTIN")) { + /* + * This makes no sense + */ + tevent_req_nterror(req, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND); + return tevent_req_post(req, ev); + } + + if (strequal(domain_name, get_global_sam_name())) { + int role = lp_server_role(); + if ( role != ROLE_ACTIVE_DIRECTORY_DC ) { + /* + * Two options here: Give back our own address, or say there's + * nobody around. Right now opting for the latter, one measure + * to prevent the loopback connects. This might change if + * needed. + */ + tevent_req_nterror(req, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND); + return tevent_req_post(req, ev); + } + } + + if (IS_DC) { + /* + * We have to figure out the DC ourselves + */ + child_binding_handle = locator_child_handle(); + } else { + struct winbindd_domain *domain = find_our_domain(); + child_binding_handle = dom_child_handle(domain); + } + + if (domain_guid != NULL) { + /* work around a const issue in rpccli_ autogenerated code */ + state->domain_guid = *domain_guid; + guid_ptr = &state->domain_guid; + } + + state->domain_name = talloc_strdup(state, domain_name); + if (tevent_req_nomem(state->domain_name, req)) { + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_DsGetDcName_send( + state, ev, child_binding_handle, domain_name, guid_ptr, site_name, + flags, &state->dcinfo); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_dsgetdcname_done, req); + return req; +} + +static void wb_dsgetdcname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_dsgetdcname_state *state = tevent_req_data( + req, struct wb_dsgetdcname_state); + NTSTATUS status, result; + + status = dcerpc_wbint_DsGetDcName_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_dsgetdcname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct netr_DsRGetDCNameInfo **pdcinfo) +{ + struct wb_dsgetdcname_state *state = tevent_req_data( + req, struct wb_dsgetdcname_state); + NTSTATUS status; + + D_INFO("WB command dsgetdcname for %s end.\n", + state->domain_name); + if (tevent_req_is_nterror(req, &status)) { + D_NOTICE("Failed for %s with %s.\n", + state->domain_name, + nt_errstr(status)); + return status; + } + *pdcinfo = talloc_move(mem_ctx, &state->dcinfo); + return NT_STATUS_OK; +} + +NTSTATUS wb_dsgetdcname_gencache_set(const char *domname, + struct netr_DsRGetDCNameInfo *dcinfo) +{ + DATA_BLOB blob; + enum ndr_err_code ndr_err; + char *key; + bool ok; + + key = talloc_asprintf_strupper_m(talloc_tos(), "DCINFO/%s", domname); + if (key == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (DEBUGLEVEL >= DBGLVL_DEBUG) { + NDR_PRINT_DEBUG(netr_DsRGetDCNameInfo, dcinfo); + } + + ndr_err = ndr_push_struct_blob( + &blob, key, dcinfo, + (ndr_push_flags_fn_t)ndr_push_netr_DsRGetDCNameInfo); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + DBG_WARNING("ndr_push_struct_blob failed: %s\n", + ndr_errstr(ndr_err)); + TALLOC_FREE(key); + return status; + } + + ok = gencache_set_data_blob(key, blob, time(NULL)+3600); + + if (!ok) { + DBG_WARNING("gencache_set_data_blob for key %s failed\n", key); + TALLOC_FREE(key); + return NT_STATUS_UNSUCCESSFUL; + } + + TALLOC_FREE(key); + return NT_STATUS_OK; +} + +struct dcinfo_parser_state { + NTSTATUS status; + TALLOC_CTX *mem_ctx; + struct netr_DsRGetDCNameInfo *dcinfo; +}; + +static void dcinfo_parser(const struct gencache_timeout *timeout, + DATA_BLOB blob, + void *private_data) +{ + struct dcinfo_parser_state *state = private_data; + enum ndr_err_code ndr_err; + + if (gencache_timeout_expired(timeout)) { + return; + } + + state->dcinfo = talloc(state->mem_ctx, struct netr_DsRGetDCNameInfo); + if (state->dcinfo == NULL) { + state->status = NT_STATUS_NO_MEMORY; + return; + } + + ndr_err = ndr_pull_struct_blob_all( + &blob, state->dcinfo, state->dcinfo, + (ndr_pull_flags_fn_t)ndr_pull_netr_DsRGetDCNameInfo); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_pull_struct_blob failed\n"); + state->status = ndr_map_error2ntstatus(ndr_err); + TALLOC_FREE(state->dcinfo); + return; + } + + state->status = NT_STATUS_OK; +} + +NTSTATUS wb_dsgetdcname_gencache_get(TALLOC_CTX *mem_ctx, + const char *domname, + struct netr_DsRGetDCNameInfo **dcinfo) +{ + struct dcinfo_parser_state state; + char *key; + bool ok; + + key = talloc_asprintf_strupper_m(mem_ctx, "DCINFO/%s", domname); + if (key == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state = (struct dcinfo_parser_state) { + .status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND, + .mem_ctx = mem_ctx, + }; + + ok = gencache_parse(key, dcinfo_parser, &state); + TALLOC_FREE(key); + if (!ok) { + return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + } + + if (!NT_STATUS_IS_OK(state.status)) { + return state.status; + } + + if (DEBUGLEVEL >= DBGLVL_DEBUG) { + NDR_PRINT_DEBUG(netr_DsRGetDCNameInfo, state.dcinfo); + } + + *dcinfo = state.dcinfo; + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_getgrsid.c b/source3/winbindd/wb_getgrsid.c new file mode 100644 index 0000000..4fd696d --- /dev/null +++ b/source3/winbindd/wb_getgrsid.c @@ -0,0 +1,403 @@ +/* + Unix SMB/CIFS implementation. + async getgrsid + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" +#include "lib/dbwrap/dbwrap_rbt.h" +#include "lib/dbwrap/dbwrap.h" + +struct wb_getgrsid_state { + struct tevent_context *ev; + struct dom_sid sid; + int max_nesting; + const char *domname; + const char *name; + enum lsa_SidType type; + gid_t gid; + struct db_context *members; + uint32_t num_sids; + struct dom_sid *sids; +}; + +static void wb_getgrsid_lookupsid_done(struct tevent_req *subreq); +static void wb_getgrsid_sid2gid_done(struct tevent_req *subreq); +static void wb_getgrsid_got_members(struct tevent_req *subreq); +static void wb_getgrsid_got_alias_members(struct tevent_req *subreq); + +struct tevent_req *wb_getgrsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *group_sid, + int max_nesting) +{ + struct tevent_req *req, *subreq; + struct wb_getgrsid_state *state; + struct dom_sid_buf buf; + + req = tevent_req_create(mem_ctx, &state, struct wb_getgrsid_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command getgrsid start.\nLooking up group SID %s.\n", dom_sid_str_buf(group_sid, &buf)); + + sid_copy(&state->sid, group_sid); + state->ev = ev; + state->max_nesting = max_nesting; + + if (dom_sid_in_domain(&global_sid_Unix_Groups, group_sid)) { + /* unmapped Unix groups must be resolved locally */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = wb_lookupsid_send(state, ev, &state->sid); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_getgrsid_lookupsid_done, req); + return req; +} + +static void wb_getgrsid_lookupsid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_getgrsid_state *state = tevent_req_data( + req, struct wb_getgrsid_state); + NTSTATUS status; + + status = wb_lookupsid_recv(subreq, state, &state->type, + &state->domname, &state->name); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + switch (state->type) { + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + /* + * also treat user-type SIDS (they might map to ID_TYPE_BOTH) + */ + case SID_NAME_USER: + case SID_NAME_COMPUTER: + break; + default: + tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP); + return; + } + + subreq = wb_sids2xids_send(state, state->ev, &state->sid, 1); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_getgrsid_sid2gid_done, req); +} + +static void wb_getgrsid_sid2gid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_getgrsid_state *state = tevent_req_data( + req, struct wb_getgrsid_state); + NTSTATUS status; + struct unixid xids[1]; + + status = wb_sids2xids_recv(subreq, xids, ARRAY_SIZE(xids)); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + /* + * We are filtering further down in sids2xids, but that filtering + * depends on the actual type of the sid handed in (as determined + * by lookupsids). Here we need to filter for the type of object + * actually requested, in this case uid. + */ + if (!(xids[0].type == ID_TYPE_GID || xids[0].type == ID_TYPE_BOTH)) { + tevent_req_nterror(req, NT_STATUS_NONE_MAPPED); + return; + } + + state->gid = (gid_t)xids[0].id; + + switch (state->type) { + case SID_NAME_USER: + case SID_NAME_COMPUTER: { + /* + * special treatment for a user sid that is + * mapped to ID_TYPE_BOTH: + * create a group with the sid/xid as only member + */ + const char *name; + + if (xids[0].type != ID_TYPE_BOTH) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP); + return; + } + + state->members = db_open_rbt(state); + if (tevent_req_nomem(state->members, req)) { + return; + } + + name = fill_domain_username_talloc(talloc_tos(), + state->domname, + state->name, + true /* can_assume */); + if (tevent_req_nomem(name, req)) { + return; + } + + status = add_member_to_db(state->members, &state->sid, name); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + + tevent_req_done(req); + return; + } + case SID_NAME_ALIAS: + subreq = wb_alias_members_send(state, + state->ev, + &state->sid, + state->type, + state->max_nesting); + if (tevent_req_nomem(subreq, req)) { + return; + } + /* Decrement the depth based on 'winbind expand groups' */ + state->max_nesting--; + tevent_req_set_callback(subreq, + wb_getgrsid_got_alias_members, + req); + break; + case SID_NAME_DOM_GRP: + subreq = wb_group_members_send(state, + state->ev, + &state->sid, + 1, + &state->type, + state->max_nesting); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_getgrsid_got_members, req); + break; + case SID_NAME_WKN_GRP: + state->members = db_open_rbt(state); + if (tevent_req_nomem(state->members, req)) { + return; + } + tevent_req_done(req); + return; + default: + tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP); + break; + } +} + +static void wb_getgrsid_got_alias_members_names(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct wb_getgrsid_state *state = + tevent_req_data(req, struct wb_getgrsid_state); + struct lsa_RefDomainList *domains = NULL; + struct lsa_TransNameArray *names = NULL; + NTSTATUS status; + uint32_t li; + uint32_t num_sids = 0; + struct dom_sid *sids = NULL; + enum lsa_SidType *types = NULL; + + status = wb_lookupsids_recv(subreq, state, &domains, &names); + + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return; + } + + if (domains == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n"); + return; + } + + if (names == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n"); + return; + } + + state->members = db_open_rbt(state); + if (tevent_req_nomem(state->members, req)) { + return; + } + + for (li = 0; li < state->num_sids; li++) { + struct lsa_TranslatedName *n = &names->names[li]; + + if (n->sid_type == SID_NAME_USER || + n->sid_type == SID_NAME_COMPUTER) { + const char *name = fill_domain_username_talloc( + talloc_tos(), + domains->domains[n->sid_index].name.string, + n->name.string, + false /* can_assume */); + if (tevent_req_nomem(name, req)) { + return; + } + + status = add_member_to_db(state->members, + &state->sids[li], + name); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + } else if (n->sid_type == SID_NAME_DOM_GRP) { + sids = talloc_realloc(talloc_tos(), + sids, + struct dom_sid, + num_sids + 1); + if (tevent_req_nomem(sids, req)) { + return; + } + sids[num_sids] = state->sids[li]; + types = talloc_realloc(talloc_tos(), + types, + enum lsa_SidType, + num_sids + 1); + if (tevent_req_nomem(types, req)) { + return; + } + types[num_sids] = n->sid_type; + num_sids++; + } else { + struct dom_sid_buf buf; + D_DEBUG("SID %s with sid_type=%d is ignored!\n", + dom_sid_str_buf(&state->sids[li], &buf), + n->sid_type); + } + } + + TALLOC_FREE(names); + TALLOC_FREE(domains); + + if (num_sids == 0) { + tevent_req_done(req); + return; + } + subreq = wb_group_members_send(state, + state->ev, + sids, + num_sids, + types, + state->max_nesting); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_getgrsid_got_members, req); +} + +static void wb_getgrsid_got_alias_members(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, struct tevent_req); + struct wb_getgrsid_state *state = + tevent_req_data(req, struct wb_getgrsid_state); + NTSTATUS status; + + status = wb_alias_members_recv(subreq, + state, + &state->num_sids, + &state->sids); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = wb_lookupsids_send(state, + state->ev, + state->sids, + state->num_sids); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + wb_getgrsid_got_alias_members_names, + req); +} + +static void wb_getgrsid_got_members(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_getgrsid_state *state = tevent_req_data( + req, struct wb_getgrsid_state); + NTSTATUS status; + struct db_context *members_prev = state->members; + + status = wb_group_members_recv(subreq, state, &state->members); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + /* + * If we have called wb_alias_members_send(), members_prev + * might already contain users that are direct members of alias, + * add to them the users from nested groups. + */ + if (members_prev != NULL) { + status = dbwrap_merge_dbs(state->members, + members_prev, + TDB_REPLACE); + if (tevent_req_nterror(req, status)) { + return; + } + } + tevent_req_done(req); +} + +NTSTATUS wb_getgrsid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + const char **domname, const char **name, gid_t *gid, + struct db_context **members) +{ + struct wb_getgrsid_state *state = tevent_req_data( + req, struct wb_getgrsid_state); + NTSTATUS status; + + D_INFO("WB command getgrsid end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + *domname = talloc_move(mem_ctx, &state->domname); + *name = talloc_move(mem_ctx, &state->name); + *gid = state->gid; + *members = talloc_move(mem_ctx, &state->members); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_getpwsid.c b/source3/winbindd/wb_getpwsid.c new file mode 100644 index 0000000..7d04c39 --- /dev/null +++ b/source3/winbindd/wb_getpwsid.c @@ -0,0 +1,156 @@ +/* + Unix SMB/CIFS implementation. + async getpwsid + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" +#include "lib/util/string_wrappers.h" +#include "source3/lib/substitute.h" + +struct wb_getpwsid_state { + struct tevent_context *ev; + struct dom_sid sid; + struct wbint_userinfo *userinfo; + struct winbindd_pw *pw; +}; + +static void wb_getpwsid_queryuser_done(struct tevent_req *subreq); + +struct tevent_req *wb_getpwsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *user_sid, + struct winbindd_pw *pw) +{ + struct tevent_req *req, *subreq; + struct wb_getpwsid_state *state; + struct dom_sid_buf buf; + + req = tevent_req_create(mem_ctx, &state, struct wb_getpwsid_state); + if (req == NULL) { + return NULL; + } + D_INFO("WB command getpwsid start.\nQuery user SID %s.\n", dom_sid_str_buf(user_sid, &buf)); + sid_copy(&state->sid, user_sid); + state->ev = ev; + state->pw = pw; + + if (dom_sid_in_domain(&global_sid_Unix_Users, user_sid)) { + /* unmapped Unix users must be resolved locally */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = wb_queryuser_send(state, ev, &state->sid); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_getpwsid_queryuser_done, req); + return req; +} + +static void wb_getpwsid_queryuser_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_getpwsid_state *state = tevent_req_data( + req, struct wb_getpwsid_state); + struct winbindd_pw *pw = state->pw; + struct wbint_userinfo *info; + fstring acct_name; + const char *output_username = NULL; + char *mapped_name = NULL; + char *tmp; + NTSTATUS status; + + status = wb_queryuser_recv(subreq, state, &state->userinfo); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + info = state->userinfo; + + pw->pw_uid = info->uid; + pw->pw_gid = info->primary_gid; + + fstrcpy(acct_name, info->acct_name); + if (!strlower_m(acct_name)) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + + /* + * TODO: + * This function should be called in 'idmap winbind child'. It shouldn't + * be a blocking call, but for this we need to add a new function for + * winbind.idl. This is a fix which can be backported for now. + */ + status = normalize_name_map(state, + info->domain_name, + acct_name, + &mapped_name); + if (NT_STATUS_IS_OK(status) || + NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) { + fstrcpy(acct_name, mapped_name); + } + output_username = fill_domain_username_talloc(state, + info->domain_name, + acct_name, + true); + if (output_username == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return; + } + + strlcpy(pw->pw_name, output_username, sizeof(pw->pw_name)); + + strlcpy(pw->pw_gecos, info->full_name ? info->full_name : "", + sizeof(pw->pw_gecos)); + + tmp = talloc_sub_specified( + state, info->homedir, acct_name, + info->primary_group_name, info->domain_name, + pw->pw_uid, pw->pw_gid); + if (tevent_req_nomem(tmp, req)) { + return; + } + strlcpy(pw->pw_dir, tmp, sizeof(pw->pw_dir)); + TALLOC_FREE(tmp); + + tmp = talloc_sub_specified( + state, info->shell, acct_name, + info->primary_group_name, info->domain_name, + pw->pw_uid, pw->pw_gid); + if (tevent_req_nomem(tmp, req)) { + return; + } + strlcpy(pw->pw_shell, tmp, sizeof(pw->pw_shell)); + TALLOC_FREE(tmp); + + strlcpy(pw->pw_passwd, "*", sizeof(pw->pw_passwd)); + + tevent_req_done(req); +} + +NTSTATUS wb_getpwsid_recv(struct tevent_req *req) +{ + NTSTATUS status = tevent_req_simple_recv_ntstatus(req); + D_INFO("WB command getpwsid end.\nReturn status %s.\n", nt_errstr(status)); + return status; +} diff --git a/source3/winbindd/wb_gettoken.c b/source3/winbindd/wb_gettoken.c new file mode 100644 index 0000000..3930f71 --- /dev/null +++ b/source3/winbindd/wb_gettoken.c @@ -0,0 +1,290 @@ +/* + Unix SMB/CIFS implementation. + async gettoken + Copyright (C) Volker Lendecke 2009 + + 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 "util/debug.h" +#include "winbindd.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" +#include "passdb/machine_sid.h" + +struct wb_gettoken_state { + struct tevent_context *ev; + struct dom_sid usersid; + bool expand_local_aliases; + uint32_t num_sids; + struct dom_sid *sids; +}; + +static NTSTATUS wb_add_rids_to_sids(TALLOC_CTX *mem_ctx, + uint32_t *pnum_sids, + struct dom_sid **psids, + const struct dom_sid *domain_sid, + uint32_t num_rids, uint32_t *rids); + +static void wb_gettoken_gotuser(struct tevent_req *subreq); +static void wb_gettoken_gotgroups(struct tevent_req *subreq); +static void wb_gettoken_gotlocalgroups(struct tevent_req *subreq); +static void wb_gettoken_gotbuiltins(struct tevent_req *subreq); + +struct tevent_req *wb_gettoken_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid, + bool expand_local_aliases) +{ + struct tevent_req *req, *subreq; + struct wb_gettoken_state *state; + struct dom_sid_buf buf; + + req = tevent_req_create(mem_ctx, &state, struct wb_gettoken_state); + if (req == NULL) { + return NULL; + } + sid_copy(&state->usersid, sid); + state->ev = ev; + state->expand_local_aliases = expand_local_aliases; + + D_INFO("WB command gettoken start.\n" + "Query user SID %s (expand local aliases is %d).\n", + dom_sid_str_buf(sid, &buf), + expand_local_aliases); + subreq = wb_queryuser_send(state, ev, &state->usersid); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_gettoken_gotuser, req); + return req; +} + +static void wb_gettoken_gotuser(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_gettoken_state *state = tevent_req_data( + req, struct wb_gettoken_state); + struct wbint_userinfo *info; + NTSTATUS status; + struct dom_sid_buf buf0, buf1; + + status = wb_queryuser_recv(subreq, state, &info); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->sids = talloc_array(state, struct dom_sid, 2); + if (tevent_req_nomem(state->sids, req)) { + return; + } + state->num_sids = 2; + + D_DEBUG("Got user SID %s and group SID %s\n", + dom_sid_str_buf(&info->user_sid, &buf0), + dom_sid_str_buf(&info->group_sid, &buf1)); + sid_copy(&state->sids[0], &info->user_sid); + sid_copy(&state->sids[1], &info->group_sid); + + D_DEBUG("Looking up user groups for the user SID.\n"); + subreq = wb_lookupusergroups_send(state, state->ev, &info->user_sid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_gettoken_gotgroups, req); +} + +static void wb_gettoken_gotgroups(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_gettoken_state *state = tevent_req_data( + req, struct wb_gettoken_state); + uint32_t i, num_groups; + struct dom_sid *groups; + struct winbindd_domain *domain; + NTSTATUS status; + struct dom_sid_buf buf; + + status = wb_lookupusergroups_recv(subreq, state, &num_groups, &groups); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_done(req); + return; + } + + D_DEBUG("Received %"PRIu32" group(s).\n", num_groups); + for (i = 0; i < num_groups; i++) { + D_DEBUG("Adding SID %s.\n", dom_sid_str_buf(&groups[i], &buf)); + status = add_sid_to_array_unique( + state, &groups[i], &state->sids, &state->num_sids); + + if (tevent_req_nterror(req, status)) { + return; + } + } + + if (!state->expand_local_aliases) { + D_DEBUG("Done. Not asked to expand local aliases.\n"); + tevent_req_done(req); + return; + } + + /* + * Expand our domain's aliases + */ + domain = find_domain_from_sid_noinit(get_global_sam_sid()); + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + D_DEBUG("Expand domain's aliases for %"PRIu32" SID(s).\n", + state->num_sids); + subreq = wb_lookupuseraliases_send(state, state->ev, domain, + state->num_sids, state->sids); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_gettoken_gotlocalgroups, req); +} + +static void wb_gettoken_gotlocalgroups(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_gettoken_state *state = tevent_req_data( + req, struct wb_gettoken_state); + uint32_t num_rids; + uint32_t *rids; + struct winbindd_domain *domain; + NTSTATUS status; + + status = wb_lookupuseraliases_recv(subreq, state, &num_rids, &rids); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + D_DEBUG("Got %"PRIu32" RID(s).\n", num_rids); + status = wb_add_rids_to_sids(state, &state->num_sids, &state->sids, + get_global_sam_sid(), num_rids, rids); + if (tevent_req_nterror(req, status)) { + return; + } + TALLOC_FREE(rids); + + /* + * Now expand the builtin groups + */ + + D_DEBUG("Expand the builtin groups for %"PRIu32" SID(s).\n", + state->num_sids); + domain = find_domain_from_sid(&global_sid_Builtin); + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + subreq = wb_lookupuseraliases_send(state, state->ev, domain, + state->num_sids, state->sids); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_gettoken_gotbuiltins, req); +} + +static void wb_gettoken_gotbuiltins(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_gettoken_state *state = tevent_req_data( + req, struct wb_gettoken_state); + uint32_t num_rids; + uint32_t *rids; + NTSTATUS status; + + status = wb_lookupuseraliases_recv(subreq, state, &num_rids, &rids); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + D_DEBUG("Got %"PRIu32" RID(s).\n", num_rids); + status = wb_add_rids_to_sids(state, &state->num_sids, &state->sids, + &global_sid_Builtin, num_rids, rids); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_gettoken_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint32_t *num_sids, struct dom_sid **sids) +{ + struct wb_gettoken_state *state = tevent_req_data( + req, struct wb_gettoken_state); + NTSTATUS status; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *num_sids = state->num_sids; + D_INFO("WB command gettoken end.\nReceived %"PRIu32" SID(s).\n", + state->num_sids); + + if (CHECK_DEBUGLVL(DBGLVL_INFO)) { + for (i = 0; i < state->num_sids; i++) { + struct dom_sid_buf sidbuf; + D_INFO("%"PRIu32": %s\n", + i, + dom_sid_str_buf(&state->sids[i], + &sidbuf)); + } + } + + *sids = talloc_move(mem_ctx, &state->sids); + return NT_STATUS_OK; +} + +static NTSTATUS wb_add_rids_to_sids(TALLOC_CTX *mem_ctx, + uint32_t *pnum_sids, + struct dom_sid **psids, + const struct dom_sid *domain_sid, + uint32_t num_rids, uint32_t *rids) +{ + uint32_t i; + + D_DEBUG("%"PRIu32" SID(s) will be uniquely added to the SID array.\n" + "Before the addition the array has %"PRIu32" SID(s).\n", + num_rids, *pnum_sids); + + for (i = 0; i < num_rids; i++) { + NTSTATUS status; + struct dom_sid sid; + + sid_compose(&sid, domain_sid, rids[i]); + status = add_sid_to_array_unique( + mem_ctx, &sid, psids, pnum_sids); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + D_DEBUG("After the addition the array has %"PRIu32" SID(s).\n", + *pnum_sids); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_group_members.c b/source3/winbindd/wb_group_members.c new file mode 100644 index 0000000..3fe7357 --- /dev/null +++ b/source3/winbindd/wb_group_members.c @@ -0,0 +1,489 @@ +/* + Unix SMB/CIFS implementation. + async lookupgroupmembers + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "../libcli/security/security.h" +#include "lib/util/util_tdb.h" +#include "lib/dbwrap/dbwrap.h" +#include "lib/dbwrap/dbwrap_rbt.h" + +/* + * We have 3 sets of routines here: + * + * wb_lookupgroupmem is the low-level one-group routine + * + * wb_groups_members walks a list of groups + * + * wb_group_members finally is the high-level routine expanding groups + * recursively + */ + +/* + * TODO: fill_grent_mem_domusers must be re-added + */ + +/* + * Look up members of a single group. Essentially a wrapper around the + * lookup_groupmem winbindd_methods routine. + */ + +struct wb_lookupgroupmem_state { + struct dom_sid sid; + struct wbint_Principals members; +}; + +static void wb_lookupgroupmem_done(struct tevent_req *subreq); + +static struct tevent_req *wb_lookupgroupmem_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *group_sid, + enum lsa_SidType type) +{ + struct tevent_req *req, *subreq; + struct wb_lookupgroupmem_state *state; + struct winbindd_domain *domain; + + req = tevent_req_create(mem_ctx, &state, + struct wb_lookupgroupmem_state); + if (req == NULL) { + return NULL; + } + sid_copy(&state->sid, group_sid); + + domain = find_domain_from_sid_noinit(group_sid); + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_LookupGroupMembers_send( + state, ev, dom_child_handle(domain), &state->sid, type, + &state->members); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_lookupgroupmem_done, req); + return req; +} + +static void wb_lookupgroupmem_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_lookupgroupmem_state *state = tevent_req_data( + req, struct wb_lookupgroupmem_state); + NTSTATUS status, result; + + status = dcerpc_wbint_LookupGroupMembers_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +static NTSTATUS wb_lookupgroupmem_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint32_t *num_members, + struct wbint_Principal **members) +{ + struct wb_lookupgroupmem_state *state = tevent_req_data( + req, struct wb_lookupgroupmem_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + + *num_members = state->members.num_principals; + *members = talloc_move(mem_ctx, &state->members.principals); + return NT_STATUS_OK; +} + +/* + * Same as wb_lookupgroupmem for a list of groups + */ + +struct wb_groups_members_state { + struct tevent_context *ev; + struct wbint_Principal *groups; + uint32_t num_groups; + uint32_t next_group; + struct wbint_Principal *all_members; +}; + +static NTSTATUS wb_groups_members_next_subreq( + struct wb_groups_members_state *state, + TALLOC_CTX *mem_ctx, struct tevent_req **psubreq); +static void wb_groups_members_done(struct tevent_req *subreq); + +static struct tevent_req *wb_groups_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t num_groups, + struct wbint_Principal *groups) +{ + struct tevent_req *req, *subreq = NULL; + struct wb_groups_members_state *state; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct wb_groups_members_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->groups = groups; + state->num_groups = num_groups; + state->next_group = 0; + state->all_members = NULL; + + D_DEBUG("Looking up %"PRIu32" group(s).\n", num_groups); + status = wb_groups_members_next_subreq(state, state, &subreq); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + if (subreq == NULL) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_groups_members_done, req); + return req; +} + +static NTSTATUS wb_groups_members_next_subreq( + struct wb_groups_members_state *state, + TALLOC_CTX *mem_ctx, struct tevent_req **psubreq) +{ + struct tevent_req *subreq; + struct wbint_Principal *g; + + if (state->next_group >= state->num_groups) { + *psubreq = NULL; + return NT_STATUS_OK; + } + + g = &state->groups[state->next_group]; + state->next_group += 1; + + subreq = wb_lookupgroupmem_send(mem_ctx, state->ev, &g->sid, g->type); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + *psubreq = subreq; + return NT_STATUS_OK; +} + +static void wb_groups_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_groups_members_state *state = tevent_req_data( + req, struct wb_groups_members_state); + uint32_t i, num_all_members; + uint32_t num_members = 0; + struct wbint_Principal *members = NULL; + NTSTATUS status; + + status = wb_lookupgroupmem_recv(subreq, state, &num_members, &members); + TALLOC_FREE(subreq); + + /* + * In this error handling here we might have to be a bit more generous + * and just continue if an error occurred. + */ + + if (!NT_STATUS_IS_OK(status)) { + if (!NT_STATUS_EQUAL( + status, NT_STATUS_TRUSTED_DOMAIN_FAILURE)) { + tevent_req_nterror(req, status); + return; + } + num_members = 0; + } + + num_all_members = talloc_array_length(state->all_members); + + D_DEBUG("Adding %"PRIu32" new member(s) to existing %"PRIu32" member(s)\n", + num_members, + num_all_members); + + state->all_members = talloc_realloc( + state, state->all_members, struct wbint_Principal, + num_all_members + num_members); + if ((num_all_members + num_members != 0) + && tevent_req_nomem(state->all_members, req)) { + return; + } + for (i=0; i<num_members; i++) { + struct wbint_Principal *src, *dst; + src = &members[i]; + dst = &state->all_members[num_all_members + i]; + sid_copy(&dst->sid, &src->sid); + dst->name = talloc_move(state->all_members, &src->name); + dst->type = src->type; + } + TALLOC_FREE(members); + + status = wb_groups_members_next_subreq(state, state, &subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (subreq == NULL) { + tevent_req_done(req); + return; + } + tevent_req_set_callback(subreq, wb_groups_members_done, req); +} + +static NTSTATUS wb_groups_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint32_t *num_members, + struct wbint_Principal **members) +{ + struct wb_groups_members_state *state = tevent_req_data( + req, struct wb_groups_members_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *num_members = talloc_array_length(state->all_members); + *members = talloc_move(mem_ctx, &state->all_members); + return NT_STATUS_OK; +} + + +/* + * This is the routine expanding a list of groups up to a certain level. We + * collect the users in a rbt database: We have to add them without duplicates, + * and the db is indexed by SID. + */ + +struct wb_group_members_state { + struct tevent_context *ev; + int depth; + struct db_context *users; + struct wbint_Principal *groups; +}; + +static NTSTATUS wb_group_members_next_subreq( + struct wb_group_members_state *state, + TALLOC_CTX *mem_ctx, struct tevent_req **psubreq); +static void wb_group_members_done(struct tevent_req *subreq); + +struct tevent_req *wb_group_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid, + uint32_t num_sids, + enum lsa_SidType *type, + int max_depth) +{ + struct tevent_req *req, *subreq = NULL; + struct wb_group_members_state *state; + NTSTATUS status; + struct dom_sid_buf buf; + uint32_t i; + + req = tevent_req_create(mem_ctx, &state, + struct wb_group_members_state); + if (req == NULL) { + return NULL; + } + D_INFO("WB command group_members start (max_depth=%d).\n", max_depth); + for (i = 0; i < num_sids; i++) { + D_INFO("Looking up members of group SID %s with SID type %d\n", + dom_sid_str_buf(&sid[i], &buf), + type[i]); + } + + state->ev = ev; + state->depth = max_depth; + state->users = db_open_rbt(state); + if (tevent_req_nomem(state->users, req)) { + return tevent_req_post(req, ev); + } + + state->groups = talloc_array(state, struct wbint_Principal, num_sids); + if (tevent_req_nomem(state->groups, req)) { + return tevent_req_post(req, ev); + } + + for (i = 0; i < num_sids; i++) { + state->groups[i].name = NULL; + sid_copy(&state->groups[i].sid, &sid[i]); + state->groups[i].type = type[i]; + } + + status = wb_group_members_next_subreq(state, state, &subreq); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + if (subreq == NULL) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_group_members_done, req); + return req; +} + +static NTSTATUS wb_group_members_next_subreq( + struct wb_group_members_state *state, + TALLOC_CTX *mem_ctx, struct tevent_req **psubreq) +{ + struct tevent_req *subreq; + + if ((talloc_array_length(state->groups) == 0) + || (state->depth <= 0)) { + *psubreq = NULL; + D_DEBUG("Finished. The depth is %d.\n", state->depth); + return NT_STATUS_OK; + } + state->depth -= 1; + + D_DEBUG("The depth is decremented to %d.\n", state->depth); + subreq = wb_groups_members_send( + mem_ctx, state->ev, talloc_array_length(state->groups), + state->groups); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + *psubreq = subreq; + return NT_STATUS_OK; +} + +NTSTATUS add_member_to_db(struct db_context *db, struct dom_sid *sid, + const char *name) +{ + size_t len = ndr_size_dom_sid(sid, 0); + uint8_t sidbuf[len]; + TDB_DATA key = { .dptr = sidbuf, .dsize = sizeof(sidbuf) }; + NTSTATUS status; + + sid_linearize(sidbuf, sizeof(sidbuf), sid); + + status = dbwrap_store(db, key, string_term_tdb_data(name), 0); + return status; +} + +static void wb_group_members_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_group_members_state *state = tevent_req_data( + req, struct wb_group_members_state); + uint32_t i, num_groups, new_groups; + uint32_t num_members = 0; + struct wbint_Principal *members = NULL; + NTSTATUS status; + + status = wb_groups_members_recv(subreq, state, &num_members, &members); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + new_groups = 0; + for (i=0; i<num_members; i++) { + switch (members[i].type) { + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + new_groups += 1; + break; + default: + /* Ignore everything else */ + break; + } + } + + num_groups = 0; + TALLOC_FREE(state->groups); + state->groups = talloc_array(state, struct wbint_Principal, + new_groups); + + /* + * Collect the users into state->users and the groups into + * state->groups for the next iteration. + */ + + for (i=0; i<num_members; i++) { + switch (members[i].type) { + case SID_NAME_USER: + case SID_NAME_COMPUTER: { + /* + * Add a copy of members[i] to state->users + */ + status = add_member_to_db(state->users, &members[i].sid, + members[i].name); + if (tevent_req_nterror(req, status)) { + return; + } + + break; + } + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: { + struct wbint_Principal *g; + /* + * Save members[i] for the next round + */ + g = &state->groups[num_groups]; + sid_copy(&g->sid, &members[i].sid); + g->name = talloc_move(state->groups, &members[i].name); + g->type = members[i].type; + num_groups += 1; + break; + } + default: + /* Ignore everything else */ + break; + } + } + + status = wb_group_members_next_subreq(state, state, &subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (subreq == NULL) { + tevent_req_done(req); + return; + } + tevent_req_set_callback(subreq, wb_group_members_done, req); +} + +NTSTATUS wb_group_members_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct db_context **members) +{ + struct wb_group_members_state *state = tevent_req_data( + req, struct wb_group_members_state); + NTSTATUS status; + + D_INFO("WB command group_members end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + *members = talloc_move(mem_ctx, &state->users); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_lookupname.c b/source3/winbindd/wb_lookupname.c new file mode 100644 index 0000000..12dbfbe --- /dev/null +++ b/source3/winbindd/wb_lookupname.c @@ -0,0 +1,123 @@ +/* + Unix SMB/CIFS implementation. + async lookupname + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" + +struct wb_lookupname_state { + struct tevent_context *ev; + const char *dom_name; + const char *name; + uint32_t flags; + struct dom_sid sid; + enum lsa_SidType type; +}; + +static void wb_lookupname_done(struct tevent_req *subreq); + +struct tevent_req *wb_lookupname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *namespace, + const char *dom_name, + const char *name, + uint32_t flags) +{ + struct tevent_req *req, *subreq; + struct wb_lookupname_state *state; + struct winbindd_domain *domain; + + req = tevent_req_create(mem_ctx, &state, struct wb_lookupname_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command lookupname start.\n" + "Search namespace '%s' and domain '%s' for name '%s'.\n", + namespace, dom_name, name); + state->ev = ev; + state->flags = flags; + + /* + * Uppercase domain and name so that we become cache-friendly + */ + state->dom_name = talloc_strdup_upper(state, dom_name); + if (tevent_req_nomem(state->dom_name, req)) { + return tevent_req_post(req, ev); + } + state->name = talloc_strdup_upper(state, name); + if (tevent_req_nomem(state->name, req)) { + return tevent_req_post(req, ev); + } + + domain = find_lookup_domain_from_name(namespace); + if (domain == NULL) { + D_WARNING("Could not find domain for %s\n", namespace); + tevent_req_nterror(req, NT_STATUS_NONE_MAPPED); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_LookupName_send( + state, ev, dom_child_handle(domain), + state->dom_name, state->name, + flags, &state->type, &state->sid); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_lookupname_done, req); + return req; +} + +static void wb_lookupname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_lookupname_state *state = tevent_req_data( + req, struct wb_lookupname_state); + NTSTATUS status, result; + + status = dcerpc_wbint_LookupName_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_lookupname_recv(struct tevent_req *req, struct dom_sid *sid, + enum lsa_SidType *type) +{ + struct wb_lookupname_state *state = tevent_req_data( + req, struct wb_lookupname_state); + NTSTATUS status; + struct dom_sid_buf buf; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + sid_copy(sid, &state->sid); + *type = state->type; + D_INFO("WB command lookupname end.\n" + "Found SID %s with SID type %d.\n", + dom_sid_str_buf(sid, &buf), + *type); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_lookupsid.c b/source3/winbindd/wb_lookupsid.c new file mode 100644 index 0000000..31820f9 --- /dev/null +++ b/source3/winbindd/wb_lookupsid.c @@ -0,0 +1,114 @@ +/* + Unix SMB/CIFS implementation. + async lookupsid + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" + +struct wb_lookupsid_state { + struct tevent_context *ev; + struct winbindd_domain *lookup_domain; + struct dom_sid sid; + enum lsa_SidType type; + const char *domname; + const char *name; +}; + +static void wb_lookupsid_done(struct tevent_req *subreq); + +struct tevent_req *wb_lookupsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid) +{ + struct tevent_req *req, *subreq; + struct wb_lookupsid_state *state; + struct dom_sid_buf buf; + + req = tevent_req_create(mem_ctx, &state, struct wb_lookupsid_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command lookupsid start.\n"); + sid_copy(&state->sid, sid); + state->ev = ev; + + state->lookup_domain = find_lookup_domain_from_sid(sid); + if (state->lookup_domain == NULL) { + D_WARNING("Could not find domain for sid %s\n", + dom_sid_str_buf(sid, &buf)); + tevent_req_nterror(req, NT_STATUS_NONE_MAPPED); + return tevent_req_post(req, ev); + } + + D_DEBUG("Looking up SID %s in domain %s.\n", + dom_sid_str_buf(&state->sid, &buf), + state->lookup_domain->name); + subreq = dcerpc_wbint_LookupSid_send( + state, ev, dom_child_handle(state->lookup_domain), + &state->sid, &state->type, &state->domname, &state->name); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_lookupsid_done, req); + return req; +} + +static void wb_lookupsid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_lookupsid_state *state = tevent_req_data( + req, struct wb_lookupsid_state); + NTSTATUS status, result; + + status = dcerpc_wbint_LookupSid_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_lookupsid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + enum lsa_SidType *type, const char **domain, + const char **name) +{ + struct wb_lookupsid_state *state = tevent_req_data( + req, struct wb_lookupsid_state); + NTSTATUS status; + struct dom_sid_buf buf; + + D_INFO("WB command lookupsid end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + *type = state->type; + *domain = talloc_move(mem_ctx, &state->domname); + *name = talloc_move(mem_ctx, &state->name); + D_INFO("SID %s has name '%s' with type '%d' in domain '%s'.\n", + dom_sid_str_buf(&state->sid, &buf), + *name, + *type, + *domain); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_lookupsids.c b/source3/winbindd/wb_lookupsids.c new file mode 100644 index 0000000..828e79e --- /dev/null +++ b/source3/winbindd/wb_lookupsids.c @@ -0,0 +1,699 @@ +/* + Unix SMB/CIFS implementation. + async lookupsids + Copyright (C) Volker Lendecke 2011 + + 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.h" +#include "lib/util_unixsids.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" +#include "passdb/machine_sid.h" +#include "lsa.h" + +struct wb_lookupsids_domain { + struct winbindd_domain *domain; + + /* + * Array of sids to be passed into wbint_LookupSids. Preallocated with + * num_sids. + */ + struct lsa_SidArray sids; + + /* + * Indexes into wb_lookupsids_state->sids and thus + * wb_lookupsids_state->res_names. Preallocated with num_sids. + */ + uint32_t *sid_indexes; +}; + +struct wb_translated_name { + const char *domain_name; + const char *name; + enum lsa_SidType type; +}; + +static struct wb_lookupsids_domain *wb_lookupsids_get_domain( + const struct dom_sid *sid, TALLOC_CTX *mem_ctx, + struct wb_lookupsids_domain **domains, uint32_t num_sids); + +struct wb_lookupsids_state { + struct tevent_context *ev; + + /* + * SIDs passed in + */ + struct dom_sid *sids; + uint32_t num_sids; + + /* + * The domains we're using for bulk lookup via wbint_LookupRids or + * wbint_LookupSids. We expect very few domains, so we do a + * talloc_realloc and rely on talloc_array_length. + */ + struct wb_lookupsids_domain *domains; + uint32_t domains_done; + + /* + * These SIDs are looked up individually via + * wbint_LookupSid. Preallocated with num_sids. + */ + uint32_t *single_sids; + uint32_t num_single_sids; + uint32_t single_sids_done; + + /* + * Intermediate store for wbint_LookupRids to passdb. These are + * spliced into res_domains/res_names in wb_lookupsids_move_name. + */ + struct wbint_RidArray rids; + const char *domain_name; + struct wbint_Principals rid_names; + + /* + * Intermediate results for wbint_LookupSids. These results are + * spliced into res_domains/res_names in wb_lookupsids_move_name. + */ + struct lsa_RefDomainList tmp_domains; + struct lsa_TransNameArray tmp_names; + + /* + * Results + */ + struct lsa_RefDomainList *res_domains; + /* + * Indexed as "sids" in this structure + */ + struct lsa_TransNameArray *res_names; +}; + +static bool wb_lookupsids_next(struct tevent_req *req, + struct wb_lookupsids_state *state); +static void wb_lookupsids_single_done(struct tevent_req *subreq); +static void wb_lookupsids_lookuprids_done(struct tevent_req *subreq); +static void wb_lookupsids_done(struct tevent_req *subreq); + +struct tevent_req *wb_lookupsids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dom_sid *sids, + uint32_t num_sids) +{ + struct tevent_req *req; + struct wb_lookupsids_state *state; + uint32_t i; + + req = tevent_req_create(mem_ctx, &state, struct wb_lookupsids_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command lookupsids start.\nLooking up %"PRIu32" SID(s)\n", + num_sids); + if (CHECK_DEBUGLVL(DBGLVL_INFO)) { + for (i = 0; i < num_sids; i++) { + struct dom_sid_buf buf; + D_INFO("%"PRIu32": %s\n", + i, dom_sid_str_buf(&sids[i], &buf)); + } + } + + state->ev = ev; + state->sids = sids; + state->num_sids = num_sids; + + state->single_sids = talloc_zero_array(state, uint32_t, num_sids); + if (tevent_req_nomem(state->single_sids, req)) { + return tevent_req_post(req, ev); + } + + state->res_domains = talloc_zero(state, struct lsa_RefDomainList); + if (tevent_req_nomem(state->res_domains, req)) { + return tevent_req_post(req, ev); + } + state->res_domains->domains = talloc_zero_array( + state->res_domains, struct lsa_DomainInfo, num_sids); + if (tevent_req_nomem(state->res_domains->domains, req)) { + return tevent_req_post(req, ev); + } + + state->res_names = talloc_zero(state, struct lsa_TransNameArray); + if (tevent_req_nomem(state->res_names, req)) { + return tevent_req_post(req, ev); + } + state->res_names->names = talloc_zero_array( + state->res_names, struct lsa_TranslatedName, num_sids); + if (tevent_req_nomem(state->res_names->names, req)) { + return tevent_req_post(req, ev); + } + + if (num_sids == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + for (i=0; i<num_sids; i++) { + struct wb_lookupsids_domain *d; + + d = wb_lookupsids_get_domain(&sids[i], state, &state->domains, + num_sids); + if (d != NULL) { + d->sids.sids[d->sids.num_sids].sid = &sids[i]; + d->sid_indexes[d->sids.num_sids] = i; + d->sids.num_sids += 1; + } else { + state->single_sids[state->num_single_sids] = i; + state->num_single_sids += 1; + } + } + + if (!wb_lookupsids_next(req, state)) { + return tevent_req_post(req, ev); + } + return req; +} + +static bool wb_lookupsids_next(struct tevent_req *req, + struct wb_lookupsids_state *state) +{ + struct tevent_req *subreq; + + if (state->domains_done < talloc_array_length(state->domains)) { + struct wb_lookupsids_domain *d; + uint32_t i; + + d = &state->domains[state->domains_done]; + + if (d->domain->internal) { + /* + * This is only our local SAM, + * see wb_lookupsids_bulk() and + * wb_lookupsids_get_domain(). + */ + state->rids.num_rids = d->sids.num_sids; + state->rids.rids = talloc_array(state, uint32_t, + state->rids.num_rids); + if (tevent_req_nomem(state->rids.rids, req)) { + return false; + } + for (i=0; i<state->rids.num_rids; i++) { + sid_peek_rid(d->sids.sids[i].sid, + &state->rids.rids[i]); + } + subreq = dcerpc_wbint_LookupRids_send( + state, state->ev, dom_child_handle(d->domain), + &d->domain->sid, &state->rids, &state->domain_name, + &state->rid_names); + if (tevent_req_nomem(subreq, req)) { + return false; + } + tevent_req_set_callback( + subreq, wb_lookupsids_lookuprids_done, req); + return true; + } + + subreq = dcerpc_wbint_LookupSids_send( + state, state->ev, dom_child_handle(d->domain), + &d->sids, &state->tmp_domains, &state->tmp_names); + if (tevent_req_nomem(subreq, req)) { + return false; + } + tevent_req_set_callback(subreq, wb_lookupsids_done, req); + return true; + } + + if (state->single_sids_done < state->num_single_sids) { + uint32_t sid_idx; + const struct dom_sid *sid; + + sid_idx = state->single_sids[state->single_sids_done]; + sid = &state->sids[sid_idx]; + + subreq = wb_lookupsid_send(state, state->ev, sid); + if (tevent_req_nomem(subreq, req)) { + return false; + } + tevent_req_set_callback(subreq, wb_lookupsids_single_done, + req); + return true; + } + + tevent_req_done(req); + return false; +} + +/* + * Decide whether to do bulk lookupsids. We have optimizations for + * passdb via lookuprids and to remote DCs via lookupsids. + */ + +static bool wb_lookupsids_bulk(const struct dom_sid *sid) +{ + struct dom_sid_buf sidbuf; + + if (sid->num_auths != 5) { + /* + * Only do "S-1-5-21-x-y-z-rid" domains via bulk + * lookup + */ + DBG_DEBUG("No bulk setup for SID %s with %"PRIi8" subauths\n", + dom_sid_str_buf(sid, &sidbuf), + sid->num_auths); + return false; + } + + if (sid_check_is_in_our_sam(sid)) { + /* + * Passdb lookup via lookuprids + */ + DBG_DEBUG("%s is in our domain\n", + dom_sid_str_buf(sid, &sidbuf)); + return true; + } + + if (IS_DC) { + /* + * Bulk lookups to trusted DCs + */ + return (find_domain_from_sid_noinit(sid) != NULL); + } + + if (lp_server_role() != ROLE_DOMAIN_MEMBER) { + /* + * Don't do bulk lookups as standalone, the only bulk + * lookup left is for domain members. + */ + return false; + } + + if (sid_check_is_in_unix_groups(sid) || + sid_check_is_unix_groups(sid) || + sid_check_is_in_unix_users(sid) || + sid_check_is_unix_users(sid) || + sid_check_is_in_builtin(sid) || + sid_check_is_builtin(sid) || + sid_check_is_wellknown_domain(sid, NULL) || + sid_check_is_in_wellknown_domain(sid)) + { + /* + * These are locally done piece by piece anyway, no + * need for bulk optimizations. + */ + return false; + } + + /* + * All other SIDs are sent to the DC we're connected to as + * member via a single lsa_lookupsids call. + */ + return true; +} + +static struct wb_lookupsids_domain *wb_lookupsids_get_domain( + const struct dom_sid *sid, TALLOC_CTX *mem_ctx, + struct wb_lookupsids_domain **pdomains, uint32_t num_sids) +{ + struct wb_lookupsids_domain *domains, *domain; + struct winbindd_domain *wb_domain; + uint32_t i, num_domains; + + if (!wb_lookupsids_bulk(sid)) { + D_DEBUG("wb_lookupsids_bulk() is FALSE\n"); + return NULL; + } + D_DEBUG("wb_lookupsids_bulk() is TRUE\n"); + + domains = *pdomains; + num_domains = talloc_array_length(domains); + + wb_domain = find_lookup_domain_from_sid(sid); + if (wb_domain == NULL) { + return NULL; + } + + D_DEBUG("Searching %"PRIu32" domain(s) for domain '%s'\n", + num_domains, wb_domain->name); + for (i=0; i<num_domains; i++) { + if (domains[i].domain != wb_domain) { + continue; + } + + if (!domains[i].domain->internal) { + /* + * If it's not our local sam, + * we can re-use the domain without + * checking the sid. + * + * Note the wb_lookupsids_bulk() above + * already caught special SIDs, + * e.g. the unix and builtin domains. + */ + return &domains[i]; + } + + if (dom_sid_compare_domain(sid, &domains[i].domain->sid) == 0) { + /* + * If it's out local sam we can also use it. + */ + return &domains[i]; + } + + /* + * I'm not sure if this can be triggered, + * as wb_lookupsids_bulk() should also catch this, + * but we need to make sure that we don't use + * wbint_LookupRids() without a SID match. + */ + return NULL; + } + + domains = talloc_realloc( + mem_ctx, domains, struct wb_lookupsids_domain, num_domains+1); + if (domains == NULL) { + return NULL; + } + *pdomains = domains; + + domain = &domains[num_domains]; + domain->domain = wb_domain; + + domain->sids.sids = talloc_zero_array(domains, struct lsa_SidPtr, num_sids); + if (domains->sids.sids == NULL) { + goto fail; + } + domain->sids.num_sids = 0; + + domain->sid_indexes = talloc_zero_array(domains, uint32_t, num_sids); + if (domain->sid_indexes == NULL) { + TALLOC_FREE(domain->sids.sids); + goto fail; + } + return domain; + +fail: + /* + * Realloc to the state it was in before + */ + *pdomains = talloc_realloc( + mem_ctx, domains, struct wb_lookupsids_domain, num_domains); + return NULL; +} + +static bool wb_lookupsids_find_dom_idx(struct lsa_DomainInfo *domain, + struct lsa_RefDomainList *list, + uint32_t *idx) +{ + uint32_t i; + struct lsa_DomainInfo *new_domain; + + for (i=0; i<list->count; i++) { + if (dom_sid_equal(domain->sid, list->domains[i].sid)) { + *idx = i; + return true; + } + } + + new_domain = &list->domains[list->count]; + + new_domain->name.string = talloc_strdup( + list->domains, domain->name.string); + if (new_domain->name.string == NULL) { + return false; + } + + new_domain->sid = dom_sid_dup(list->domains, domain->sid); + if (new_domain->sid == NULL) { + return false; + } + + *idx = list->count; + list->count += 1; + return true; +} + +static bool wb_lookupsids_move_name(struct lsa_RefDomainList *src_domains, + struct lsa_TranslatedName *src_name, + struct lsa_RefDomainList *dst_domains, + struct lsa_TransNameArray *dst_names, + uint32_t dst_name_index) +{ + struct lsa_TranslatedName *dst_name; + struct lsa_DomainInfo *src_domain; + uint32_t src_domain_index; + uint32_t dst_domain_index = UINT32_MAX; + bool ok; + + src_domain_index = src_name->sid_index; + if ((src_domain_index != UINT32_MAX) && (src_domains != NULL)) { + if (src_domain_index >= src_domains->count) { + return false; + } + src_domain = &src_domains->domains[src_domain_index]; + + ok = wb_lookupsids_find_dom_idx(src_domain, + dst_domains, + &dst_domain_index); + if (!ok) { + return false; + } + } + + dst_name = &dst_names->names[dst_name_index]; + + dst_name->sid_type = src_name->sid_type; + dst_name->name.string = talloc_move(dst_names->names, + &src_name->name.string); + dst_name->sid_index = dst_domain_index; + dst_names->count += 1; + + return true; +} + +static void wb_lookupsids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_lookupsids_state *state = tevent_req_data( + req, struct wb_lookupsids_state); + struct wb_lookupsids_domain *d; + uint32_t i; + + NTSTATUS status, result; + + status = dcerpc_wbint_LookupSids_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (NT_STATUS_LOOKUP_ERR(result)) { + tevent_req_nterror(req, result); + return; + } + + /* + * Look at the individual states in the translated names. + */ + + d = &state->domains[state->domains_done]; + + for (i=0; i<state->tmp_names.count; i++) { + uint32_t res_sid_index = d->sid_indexes[i]; + + if (!wb_lookupsids_move_name( + &state->tmp_domains, &state->tmp_names.names[i], + state->res_domains, state->res_names, + res_sid_index)) { + tevent_req_oom(req); + return; + } + } + state->domains_done += 1; + wb_lookupsids_next(req, state); +} + +static void wb_lookupsids_single_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_lookupsids_state *state = tevent_req_data( + req, struct wb_lookupsids_state); + const char *domain_name = NULL; + const char *name = NULL; + enum lsa_SidType type = SID_NAME_UNKNOWN; + uint32_t res_sid_index; + uint32_t src_rid; + + struct dom_sid src_domain_sid; + struct lsa_DomainInfo src_domain; + struct lsa_RefDomainList src_domains; + struct lsa_RefDomainList *psrc_domains = NULL; + struct lsa_TranslatedName src_name; + + uint32_t domain_idx = UINT32_MAX; + NTSTATUS status; + bool ok; + + status = wb_lookupsid_recv(subreq, talloc_tos(), &type, + &domain_name, &name); + TALLOC_FREE(subreq); + if (NT_STATUS_LOOKUP_ERR(status)) { + tevent_req_nterror(req, status); + return; + } + + res_sid_index = state->single_sids[state->single_sids_done]; + + if ((domain_name != NULL) && (domain_name[0] != '\0')) { + /* + * Build structs with the domain name for + * wb_lookupsids_move_name(). If we didn't get a name, we will + * pass NULL and UINT32_MAX. + */ + + sid_copy(&src_domain_sid, &state->sids[res_sid_index]); + if (type != SID_NAME_DOMAIN) { + sid_split_rid(&src_domain_sid, &src_rid); + } + + src_domain.name.string = domain_name; + src_domain.sid = &src_domain_sid; + + src_domains.count = 1; + src_domains.domains = &src_domain; + psrc_domains = &src_domains; + + domain_idx = 0; + } + + src_name.sid_type = type; + src_name.name.string = name; + src_name.sid_index = domain_idx; + + ok = wb_lookupsids_move_name(psrc_domains, + &src_name, + state->res_domains, + state->res_names, + res_sid_index); + if (!ok) { + tevent_req_oom(req); + return; + } + state->single_sids_done += 1; + wb_lookupsids_next(req, state); +} + +static void wb_lookupsids_lookuprids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_lookupsids_state *state = tevent_req_data( + req, struct wb_lookupsids_state); + struct dom_sid src_domain_sid; + struct lsa_DomainInfo src_domain; + struct lsa_RefDomainList src_domains; + NTSTATUS status, result; + struct wb_lookupsids_domain *d; + uint32_t i; + + status = dcerpc_wbint_LookupRids_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (NT_STATUS_LOOKUP_ERR(result)) { + tevent_req_nterror(req, result); + return; + } + + /* + * Look at the individual states in the translated names. + */ + + d = &state->domains[state->domains_done]; + + sid_copy(&src_domain_sid, get_global_sam_sid()); + src_domain.name.string = get_global_sam_name(); + src_domain.sid = &src_domain_sid; + src_domains.count = 1; + src_domains.domains = &src_domain; + + for (i=0; i<state->rid_names.num_principals; i++) { + struct lsa_TranslatedName src_name; + uint32_t res_sid_index; + + /* + * Fake up structs for wb_lookupsids_move_name + */ + res_sid_index = d->sid_indexes[i]; + + src_name.sid_type = state->rid_names.principals[i].type; + src_name.name.string = state->rid_names.principals[i].name; + src_name.sid_index = 0; + + if (!wb_lookupsids_move_name( + &src_domains, &src_name, + state->res_domains, state->res_names, + res_sid_index)) { + tevent_req_oom(req); + return; + } + } + + state->domains_done += 1; + wb_lookupsids_next(req, state); +} + +NTSTATUS wb_lookupsids_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct lsa_RefDomainList **domains, + struct lsa_TransNameArray **names) +{ + struct wb_lookupsids_state *state = tevent_req_data( + req, struct wb_lookupsids_state); + NTSTATUS status; + + D_INFO("WB command lookupsids end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + + /* + * The returned names need to match the given sids, + * if not we have a bug in the code! + */ + if (state->res_names->count != state->num_sids) { + D_WARNING("Got %"PRIu32" returned name(s), but expected %"PRIu32"!\n", + state->res_names->count, state->num_sids); + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * Not strictly needed, but it might make debugging in the callers + * easier in future, if the talloc_array_length() returns the + * expected result... + */ + state->res_domains->domains = talloc_realloc(state->res_domains, + state->res_domains->domains, + struct lsa_DomainInfo, + state->res_domains->count); + + *domains = talloc_move(mem_ctx, &state->res_domains); + *names = talloc_move(mem_ctx, &state->res_names); + D_INFO("Returning %"PRIu32" domain(s) and %"PRIu32" name(s).\n", + (*domains)->count, + (*names)->count); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_lookupuseraliases.c b/source3/winbindd/wb_lookupuseraliases.c new file mode 100644 index 0000000..a9ad7d4 --- /dev/null +++ b/source3/winbindd/wb_lookupuseraliases.c @@ -0,0 +1,108 @@ +/* + Unix SMB/CIFS implementation. + async lookupuseraliases + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "libcli/security/dom_sid.h" + +struct wb_lookupuseraliases_state { + struct tevent_context *ev; + struct wbint_SidArray sids; + struct wbint_RidArray rids; +}; + +static void wb_lookupuseraliases_done(struct tevent_req *subreq); + +struct tevent_req *wb_lookupuseraliases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain, + uint32_t num_sids, + const struct dom_sid *sids) +{ + struct tevent_req *req, *subreq; + struct wb_lookupuseraliases_state *state; + uint32_t i; + + req = tevent_req_create(mem_ctx, &state, + struct wb_lookupuseraliases_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command lookupuseraliases start.\n" + "Query domain %s for max %"PRIu32" SID(s).\n", + domain->name, num_sids); + + for (i = 0; i < num_sids; i++) { + struct dom_sid_buf buf; + D_INFO("%"PRIu32": SID %s\n", i, dom_sid_str_buf(&sids[i], &buf)); + } + state->sids.num_sids = num_sids; + state->sids.sids = discard_const_p(struct dom_sid, sids); + + subreq = dcerpc_wbint_LookupUserAliases_send( + state, ev, dom_child_handle(domain), &state->sids, &state->rids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_lookupuseraliases_done, req); + return req; +} + +static void wb_lookupuseraliases_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_lookupuseraliases_state *state = tevent_req_data( + req, struct wb_lookupuseraliases_state); + NTSTATUS status, result; + + status = dcerpc_wbint_LookupUserAliases_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + D_WARNING("LookupUserAliases failed with %s.\n", + nt_errstr(status)); + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_lookupuseraliases_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint32_t *num_aliases, uint32_t **aliases) +{ + struct wb_lookupuseraliases_state *state = tevent_req_data( + req, struct wb_lookupuseraliases_state); + NTSTATUS status; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *num_aliases = state->rids.num_rids; + D_INFO("WB command lookupuseraliases end.\nGot %"PRIu32" alias(es):\n", + *num_aliases); + for (i = 0; i < *num_aliases; i++) { + D_INFO("%"PRIu32": RID %"PRIu32"\n", i, state->rids.rids[i]); + } + + *aliases = talloc_move(mem_ctx, &state->rids.rids); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_lookupusergroups.c b/source3/winbindd/wb_lookupusergroups.c new file mode 100644 index 0000000..7f359ee --- /dev/null +++ b/source3/winbindd/wb_lookupusergroups.c @@ -0,0 +1,120 @@ +/* + Unix SMB/CIFS implementation. + async lookupusergroups + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" + +struct wb_lookupusergroups_state { + struct tevent_context *ev; + struct dom_sid sid; + struct wbint_SidArray sids; +}; + +static void wb_lookupusergroups_done(struct tevent_req *subreq); + +struct tevent_req *wb_lookupusergroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid) +{ + struct tevent_req *req, *subreq; + struct wb_lookupusergroups_state *state; + struct winbindd_domain *domain; + NTSTATUS status; + struct dom_sid_buf buf; + + req = tevent_req_create(mem_ctx, &state, + struct wb_lookupusergroups_state); + if (req == NULL) { + return NULL; + } + D_INFO("WB command lookupusergroups start.\nLooking up SID %s.\n", + dom_sid_str_buf(sid, &buf)); + sid_copy(&state->sid, sid); + + status = lookup_usergroups_cached(state, + &state->sid, + &state->sids.num_sids, + &state->sids.sids); + if (NT_STATUS_IS_OK(status)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + domain = find_domain_from_sid_noinit(&state->sid); + if (domain == NULL) { + DBG_WARNING("could not find domain entry for sid %s\n", + dom_sid_str_buf(&state->sid, &buf)); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_LookupUserGroups_send( + state, ev, dom_child_handle(domain), &state->sid, &state->sids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_lookupusergroups_done, req); + return req; +} + +static void wb_lookupusergroups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_lookupusergroups_state *state = tevent_req_data( + req, struct wb_lookupusergroups_state); + NTSTATUS status, result; + + status = dcerpc_wbint_LookupUserGroups_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_lookupusergroups_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint32_t *num_sids, struct dom_sid **sids) +{ + struct wb_lookupusergroups_state *state = tevent_req_data( + req, struct wb_lookupusergroups_state); + NTSTATUS status; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *num_sids = state->sids.num_sids; + *sids = talloc_move(mem_ctx, &state->sids.sids); + + D_INFO("WB command lookupusergroups end.\nReceived %"PRIu32" SID(s).\n", + *num_sids); + if (CHECK_DEBUGLVL(DBGLVL_INFO)) { + for (i = 0; i < *num_sids; i++) { + struct dom_sid_buf buf; + D_INFO("%"PRIu32": %s\n", + i, dom_sid_str_buf(&(*sids)[i], &buf)); + } + } + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_next_grent.c b/source3/winbindd/wb_next_grent.c new file mode 100644 index 0000000..5c2d447 --- /dev/null +++ b/source3/winbindd/wb_next_grent.c @@ -0,0 +1,169 @@ +/* + Unix SMB/CIFS implementation. + async next_grent + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "passdb/machine_sid.h" + +struct wb_next_grent_state { + struct tevent_context *ev; + int max_nesting; + struct getgrent_state *gstate; + struct winbindd_gr *gr; + struct db_context *members; +}; + +static void wb_next_grent_fetch_done(struct tevent_req *subreq); +static void wb_next_grent_getgrsid_done(struct tevent_req *subreq); + +static void wb_next_grent_send_do(struct tevent_req *req, + struct wb_next_grent_state *state) +{ + struct tevent_req *subreq; + + if (state->gstate->next_group >= state->gstate->num_groups) { + TALLOC_FREE(state->gstate->groups); + + state->gstate->domain = wb_next_domain(state->gstate->domain); + if (state->gstate->domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_MORE_ENTRIES); + return; + } + + subreq = wb_query_group_list_send(state, state->ev, + state->gstate->domain); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_next_grent_fetch_done, req); + return; + } + + subreq = wb_getgrsid_send( + state, state->ev, + &state->gstate->groups[state->gstate->next_group].sid, + state->max_nesting); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_next_grent_getgrsid_done, req); +} + +struct tevent_req *wb_next_grent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int max_nesting, + struct getgrent_state *gstate, + struct winbindd_gr *gr) +{ + struct tevent_req *req; + struct wb_next_grent_state *state; + + req = tevent_req_create(mem_ctx, &state, struct wb_next_grent_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command next_grent start.\n"); + + state->ev = ev; + state->gstate = gstate; + state->gr = gr; + state->max_nesting = max_nesting; + + wb_next_grent_send_do(req, state); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void wb_next_grent_fetch_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_next_grent_state *state = tevent_req_data( + req, struct wb_next_grent_state); + NTSTATUS status; + + status = wb_query_group_list_recv(subreq, state->gstate, + &state->gstate->num_groups, + &state->gstate->groups); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + /* Ignore errors here, just log it */ + D_DEBUG("query_group_list for domain %s returned %s\n", + state->gstate->domain->name, nt_errstr(status)); + state->gstate->num_groups = 0; + } + + state->gstate->next_group = 0; + + wb_next_grent_send_do(req, state); +} + +static void wb_next_grent_getgrsid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_next_grent_state *state = tevent_req_data( + req, struct wb_next_grent_state); + const char *domname, *name; + NTSTATUS status; + + status = wb_getgrsid_recv(subreq, talloc_tos(), &domname, &name, + &state->gr->gr_gid, &state->members); + TALLOC_FREE(subreq); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + state->gstate->next_group += 1; + + wb_next_grent_send_do(req, state); + + return; + } else if (tevent_req_nterror(req, status)) { + return; + } + + if (!fill_grent(talloc_tos(), state->gr, domname, name, + state->gr->gr_gid)) { + D_WARNING("fill_grent failed\n"); + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return; + } + state->gstate->next_group += 1; + tevent_req_done(req); +} + +NTSTATUS wb_next_grent_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct db_context **members) +{ + struct wb_next_grent_state *state = tevent_req_data( + req, struct wb_next_grent_state); + NTSTATUS status; + + D_INFO("WB command next_grent end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + *members = talloc_move(mem_ctx, &state->members); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_next_pwent.c b/source3/winbindd/wb_next_pwent.c new file mode 100644 index 0000000..f000c64 --- /dev/null +++ b/source3/winbindd/wb_next_pwent.c @@ -0,0 +1,162 @@ +/* + Unix SMB/CIFS implementation. + async next_pwent + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "libcli/security/dom_sid.h" +#include "passdb/machine_sid.h" + +struct wb_next_pwent_state { + struct tevent_context *ev; + struct getpwent_state *gstate; + struct dom_sid next_sid; + struct winbindd_pw *pw; +}; + +static void wb_next_pwent_fetch_done(struct tevent_req *subreq); +static void wb_next_pwent_fill_done(struct tevent_req *subreq); + +static void wb_next_pwent_send_do(struct tevent_req *req, + struct wb_next_pwent_state *state) +{ + struct tevent_req *subreq; + struct dom_sid_buf buf, buf1; + + if (state->gstate->next_user >= state->gstate->rids.num_rids) { + TALLOC_FREE(state->gstate->rids.rids); + state->gstate->rids.num_rids = 0; + + state->gstate->domain = wb_next_domain(state->gstate->domain); + if (state->gstate->domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_MORE_ENTRIES); + return; + } + + D_DEBUG("Query user RID list for domain %s.\n", + state->gstate->domain->name); + subreq = dcerpc_wbint_QueryUserRidList_send( + state, state->ev, + dom_child_handle(state->gstate->domain), + &state->gstate->rids); + if (tevent_req_nomem(subreq, req)) { + return; + } + + tevent_req_set_callback(subreq, wb_next_pwent_fetch_done, req); + return; + } + + sid_compose(&state->next_sid, &state->gstate->domain->sid, + state->gstate->rids.rids[state->gstate->next_user]); + + D_DEBUG("Get pw for SID %s composed from domain SID %s and RID %"PRIu32".\n", + dom_sid_str_buf(&state->next_sid, &buf), + dom_sid_str_buf(&state->gstate->domain->sid, &buf1), + state->gstate->rids.rids[state->gstate->next_user]); + subreq = wb_getpwsid_send(state, state->ev, &state->next_sid, + state->pw); + if (tevent_req_nomem(subreq, req)) { + return; + } + + tevent_req_set_callback(subreq, wb_next_pwent_fill_done, req); +} + +struct tevent_req *wb_next_pwent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct getpwent_state *gstate, + struct winbindd_pw *pw) +{ + struct tevent_req *req; + struct wb_next_pwent_state *state; + + req = tevent_req_create(mem_ctx, &state, struct wb_next_pwent_state); + if (req == NULL) { + return NULL; + } + D_INFO("WB command next_pwent start.\n"); + state->ev = ev; + state->gstate = gstate; + state->pw = pw; + + wb_next_pwent_send_do(req, state); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void wb_next_pwent_fetch_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_next_pwent_state *state = tevent_req_data( + req, struct wb_next_pwent_state); + NTSTATUS status, result; + + status = dcerpc_wbint_QueryUserRidList_recv(subreq, state->gstate, + &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + /* Ignore errors here, just log it */ + D_DEBUG("query_user_list for domain %s returned %s\n", + state->gstate->domain->name, + nt_errstr(status)); + state->gstate->rids.num_rids = 0; + } + + state->gstate->next_user = 0; + + wb_next_pwent_send_do(req, state); +} + +static void wb_next_pwent_fill_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_next_pwent_state *state = tevent_req_data( + req, struct wb_next_pwent_state); + NTSTATUS status; + + status = wb_getpwsid_recv(subreq); + TALLOC_FREE(subreq); + /* + * When you try to enumerate users with 'getent passwd' and the user + * doesn't have a uid set we should just move on. + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + state->gstate->next_user += 1; + + wb_next_pwent_send_do(req, state); + + return; + } else if (tevent_req_nterror(req, status)) { + return; + } + state->gstate->next_user += 1; + tevent_req_done(req); +} + +NTSTATUS wb_next_pwent_recv(struct tevent_req *req) +{ + D_INFO("WB command next_pwent end.\n"); + return tevent_req_simple_recv_ntstatus(req); +} diff --git a/source3/winbindd/wb_query_group_list.c b/source3/winbindd/wb_query_group_list.c new file mode 100644 index 0000000..a71e162 --- /dev/null +++ b/source3/winbindd/wb_query_group_list.c @@ -0,0 +1,94 @@ +/* + Unix SMB/CIFS implementation. + async query_group_list + Copyright (C) Volker Lendecke 2009 + Copyright (C) Michael Adam 2015 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + + +struct wb_query_group_list_state { + struct wbint_Principals groups; +}; + +static void wb_query_group_list_done(struct tevent_req *subreq); + +struct tevent_req *wb_query_group_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain) +{ + struct tevent_req *req, *subreq; + struct wb_query_group_list_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct wb_query_group_list_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command group_list start.\nQuery domain %s\n", domain->name); + subreq = dcerpc_wbint_QueryGroupList_send(state, ev, + dom_child_handle(domain), + &state->groups); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, wb_query_group_list_done, req); + return req; +} + +static void wb_query_group_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_query_group_list_state *state = tevent_req_data( + req, struct wb_query_group_list_state); + NTSTATUS status, result; + + status = dcerpc_wbint_QueryGroupList_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + + tevent_req_done(req); +} + +NTSTATUS wb_query_group_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint32_t *num_groups, + struct wbint_Principal **groups) +{ + struct wb_query_group_list_state *state = tevent_req_data( + req, struct wb_query_group_list_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with: %s\n", nt_errstr(status)); + return status; + } + + *num_groups = state->groups.num_principals; + *groups = talloc_move(mem_ctx, &state->groups.principals); + + D_INFO("WB command group_list end.\n" + "Returning %"PRIu32" group(s).\n", *num_groups); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_query_user_list.c b/source3/winbindd/wb_query_user_list.c new file mode 100644 index 0000000..c3f52e5 --- /dev/null +++ b/source3/winbindd/wb_query_user_list.c @@ -0,0 +1,146 @@ +/* + Unix SMB/CIFS implementation. + async query_user_list + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "lib/util/strv.h" + +struct wb_query_user_list_state { + struct tevent_context *ev; + struct winbindd_domain *domain; + struct wbint_RidArray rids; + const char *domain_name; + struct wbint_Principals names; + char *users; +}; + +static void wb_query_user_list_gotrids(struct tevent_req *subreq); +static void wb_query_user_list_done(struct tevent_req *subreq); + +struct tevent_req *wb_query_user_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain) +{ + struct tevent_req *req, *subreq; + struct wb_query_user_list_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct wb_query_user_list_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command user_list start.\nQuery users in domain %s.\n", + domain->name); + state->ev = ev; + state->domain = domain; + + subreq = dcerpc_wbint_QueryUserRidList_send( + state, ev, dom_child_handle(domain), &state->rids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_query_user_list_gotrids, req); + return req; +} + +static void wb_query_user_list_gotrids(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_query_user_list_state *state = tevent_req_data( + req, struct wb_query_user_list_state); + NTSTATUS status, result; + + status = dcerpc_wbint_QueryUserRidList_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + + D_DEBUG("dcerpc_wbint_QueryUserRidList returned %"PRIu32" users\n", + state->rids.num_rids); + + subreq = dcerpc_wbint_LookupRids_send( + state, state->ev, dom_child_handle(state->domain), + &state->domain->sid, &state->rids, + &state->domain_name, &state->names); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_query_user_list_done, req); +} + +static void wb_query_user_list_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_query_user_list_state *state = tevent_req_data( + req, struct wb_query_user_list_state); + NTSTATUS status, result; + uint32_t i; + + status = dcerpc_wbint_LookupRids_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + D_DEBUG("Processing %"PRIu32" principal(s).\n", + state->names.num_principals); + for (i=0; i<state->names.num_principals; i++) { + struct wbint_Principal *p = &state->names.principals[i]; + const char *name; + int ret; + + name = fill_domain_username_talloc(state, state->domain_name, p->name, true); + if (name == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return; + } + ret = strv_add(state, &state->users, name); + if (ret != 0) { + tevent_req_nterror(req, map_nt_error_from_unix(ret)); + return; + } + D_DEBUG("%"PRIu32": Adding user %s\n", i, name); + } + + tevent_req_done(req); +} + +NTSTATUS wb_query_user_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **users) +{ + struct wb_query_user_list_state *state = tevent_req_data( + req, struct wb_query_user_list_state); + NTSTATUS status; + + D_INFO("WB command user_list end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with: %s\n", nt_errstr(status)); + return status; + } + + *users = talloc_move(mem_ctx, &state->users); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_queryuser.c b/source3/winbindd/wb_queryuser.c new file mode 100644 index 0000000..c2758f1 --- /dev/null +++ b/source3/winbindd/wb_queryuser.c @@ -0,0 +1,480 @@ +/* + Unix SMB/CIFS implementation. + async queryuser + Copyright (C) Volker Lendecke 2009 + + 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 "util/debug.h" +#include "winbindd.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" +#include "libsmb/samlogon_cache.h" +#include "librpc/gen_ndr/ndr_winbind.h" + +struct wb_queryuser_state { + struct tevent_context *ev; + struct wbint_userinfo *info; + const struct wb_parent_idmap_config *idmap_cfg; + bool tried_dclookup; +}; + +static void wb_queryuser_idmap_setup_done(struct tevent_req *subreq); +static void wb_queryuser_got_uid(struct tevent_req *subreq); +static void wb_queryuser_got_domain(struct tevent_req *subreq); +static void wb_queryuser_got_dc(struct tevent_req *subreq); +static void wb_queryuser_got_gid(struct tevent_req *subreq); +static void wb_queryuser_got_group_name(struct tevent_req *subreq); +static void wb_queryuser_done(struct tevent_req *subreq); + +struct tevent_req *wb_queryuser_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *user_sid) +{ + struct tevent_req *req, *subreq; + struct wb_queryuser_state *state; + struct wbint_userinfo *info; + struct dom_sid_buf buf; + + req = tevent_req_create(mem_ctx, &state, struct wb_queryuser_state); + if (req == NULL) { + return NULL; + } + D_INFO("WB command queryuser start.\nQuery user sid %s\n", + dom_sid_str_buf(user_sid, &buf)); + state->ev = ev; + + state->info = talloc_zero(state, struct wbint_userinfo); + if (tevent_req_nomem(state->info, req)) { + return tevent_req_post(req, ev); + } + info = state->info; + + info->primary_gid = (gid_t)-1; + + sid_copy(&info->user_sid, user_sid); + + subreq = wb_parent_idmap_setup_send(state, state->ev); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_queryuser_idmap_setup_done, req); + return req; +} + +static void wb_queryuser_idmap_setup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_queryuser_state *state = tevent_req_data( + req, struct wb_queryuser_state); + NTSTATUS status; + struct dom_sid_buf buf; + + status = wb_parent_idmap_setup_recv(subreq, &state->idmap_cfg); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("wb_parent_idmap_setup_recv() failed with %s.\n", + nt_errstr(status)); + return; + } + + D_DEBUG("Convert the user SID %s to XID.\n", + dom_sid_str_buf(&state->info->user_sid, &buf)); + subreq = wb_sids2xids_send( + state, state->ev, &state->info->user_sid, 1); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_got_uid, req); + return; +} + +static void wb_queryuser_got_uid(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_queryuser_state *state = tevent_req_data( + req, struct wb_queryuser_state); + struct wbint_userinfo *info = state->info; + struct netr_SamInfo3 *info3; + struct dcerpc_binding_handle *child_binding_handle = NULL; + struct unixid xid; + uint32_t user_rid = 0; + NTSTATUS status; + struct dom_sid_buf buf, buf1; + + status = wb_sids2xids_recv(subreq, &xid, 1); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("wb_sids2xids_recv() failed with %s.\n", + nt_errstr(status)); + return; + } + + if ((xid.type != ID_TYPE_UID) && (xid.type != ID_TYPE_BOTH)) { + D_WARNING("XID type is %d, should be ID_TYPE_UID or ID_TYPE_BOTH.\n", + xid.type); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return; + } + + D_DEBUG("Received XID %"PRIu32" for SID %s.\n", + xid.id, + dom_sid_str_buf(&info->user_sid, &buf1)); + info->uid = xid.id; + + /* + * Default the group sid to "Domain Users" in the user's + * domain. The samlogon cache or the query_user call later on + * can override this. + * TODO: There is still missing functionality to set the correct group + * sid using samlogon cache (needs to use S4USelf). + * Once this is done, remove the workaround in test_membership_user() in + * source4/torture/local/nss_tests.c + */ + sid_copy(&info->group_sid, &info->user_sid); + sid_split_rid(&info->group_sid, &user_rid); + sid_append_rid(&info->group_sid, + user_rid == DOMAIN_RID_GUEST ? DOMAIN_RID_GUESTS + : DOMAIN_RID_USERS); + + D_DEBUG("Preconfigured 'Domain Users' RID %u was used to create group SID %s from user SID %s.\n", + DOMAIN_RID_USERS, + dom_sid_str_buf(&info->group_sid, &buf), + dom_sid_str_buf(&info->user_sid, &buf1)); + + info->homedir = talloc_strdup(info, lp_template_homedir()); + D_DEBUG("Setting 'homedir' to the template '%s'.\n", info->homedir); + if (tevent_req_nomem(info->homedir, req)) { + return; + } + + info->shell = talloc_strdup(info, lp_template_shell()); + D_DEBUG("Setting 'shell' to the template '%s'.\n", info->shell); + if (tevent_req_nomem(info->shell, req)) { + return; + } + + info3 = netsamlogon_cache_get(state, &info->user_sid); + if (info3 != NULL) { + D_DEBUG("Filling data received from netsamlogon_cache\n"); + sid_compose(&info->group_sid, info3->base.domain_sid, + info3->base.primary_gid); + info->acct_name = talloc_move( + info, &info3->base.account_name.string); + info->full_name = talloc_move( + info, &info3->base.full_name.string); + + info->domain_name = talloc_move( + state, &info3->base.logon_domain.string); + + TALLOC_FREE(info3); + } + + NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info); + if (info->domain_name == NULL) { + D_DEBUG("Domain name is empty, calling wb_lookupsid_send() to get it.\n"); + subreq = wb_lookupsid_send(state, state->ev, &info->user_sid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_got_domain, req); + return; + } + + /* + * Note wb_sids2xids_send/recv was called before, + * so we're sure that wb_parent_idmap_setup_send/recv + * was already called. + */ + child_binding_handle = idmap_child_handle(); + D_DEBUG("Domain name is set, calling dcerpc_wbint_GetNssInfo_send()\n"); + subreq = dcerpc_wbint_GetNssInfo_send( + state, state->ev, child_binding_handle, info); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_done, req); +} + +static void wb_queryuser_got_domain(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_queryuser_state *state = tevent_req_data( + req, struct wb_queryuser_state); + struct wbint_userinfo *info = state->info; + enum lsa_SidType type; + struct dcerpc_binding_handle *child_binding_handle = NULL; + NTSTATUS status; + + status = wb_lookupsid_recv(subreq, state, &type, + &info->domain_name, &info->acct_name); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("wb_lookupsid_recv failed with %s.\n", + nt_errstr(status)); + return; + } + + NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info); + switch (type) { + case SID_NAME_USER: + case SID_NAME_COMPUTER: + /* + * user case: we only need the account name from lookup_sids + */ + break; + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + /* + * also treat group-type SIDs (they might map to ID_TYPE_BOTH) + */ + sid_copy(&info->group_sid, &info->user_sid); + break; + default: + D_WARNING("Unknown type:%d, return NT_STATUS_NO_SUCH_USER.\n", + type); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return; + } + + /* + * Note wb_sids2xids_send/recv was called before, + * so we're sure that wb_parent_idmap_setup_send/recv + * was already called. + */ + child_binding_handle = idmap_child_handle(); + D_DEBUG("About to call dcerpc_wbint_GetNssInfo_send()\n"); + subreq = dcerpc_wbint_GetNssInfo_send( + state, state->ev, child_binding_handle, info); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_done, req); +} + +static void wb_queryuser_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_queryuser_state *state = tevent_req_data( + req, struct wb_queryuser_state); + struct wbint_userinfo *info = state->info; + NTSTATUS status, result; + bool need_group_name = false; + const char *tmpl = NULL; + + status = dcerpc_wbint_GetNssInfo_recv(subreq, info, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("GetNssInfo failed with %s.\n", nt_errstr(status)); + return; + } + + if (NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) && + !state->tried_dclookup) { + D_DEBUG("GetNssInfo got DOMAIN_CONTROLLER_NOT_FOUND, calling wb_dsgetdcname_send()\n"); + subreq = wb_dsgetdcname_send( + state, state->ev, state->info->domain_name, NULL, NULL, + DS_RETURN_DNS_NAME); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_got_dc, req); + return; + } + + /* + * Ignore failure in "result" here. We'll try to fill in stuff + * that misses further down. + */ + + if (state->info->primary_gid == (gid_t)-1) { + D_DEBUG("Calling wb_sids2xids_send() to resolve primary gid.\n"); + subreq = wb_sids2xids_send( + state, state->ev, &info->group_sid, 1); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_got_gid, req); + return; + } + + tmpl = lp_template_homedir(); + if(strstr_m(tmpl, "%g") || strstr_m(tmpl, "%G")) { + need_group_name = true; + } + tmpl = lp_template_shell(); + if(strstr_m(tmpl, "%g") || strstr_m(tmpl, "%G")) { + need_group_name = true; + } + + if (need_group_name && state->info->primary_group_name == NULL) { + D_DEBUG("Calling wb_lookupsid_send() to resolve primary group name.\n"); + subreq = wb_lookupsid_send(state, state->ev, &info->group_sid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_got_group_name, + req); + return; + } + + NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info); + tevent_req_done(req); +} + +static void wb_queryuser_got_dc(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_queryuser_state *state = tevent_req_data( + req, struct wb_queryuser_state); + struct wbint_userinfo *info = state->info; + struct netr_DsRGetDCNameInfo *dcinfo; + struct dcerpc_binding_handle *child_binding_handle = NULL; + NTSTATUS status; + + status = wb_dsgetdcname_recv(subreq, state, &dcinfo); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("wb_dsgetdcname_recv() failed with %s.\n", + nt_errstr(status)); + return; + } + + state->tried_dclookup = true; + + D_DEBUG("Got DC name, calling wb_dsgetdcname_gencache_set().\n"); + status = wb_dsgetdcname_gencache_set(info->domain_name, dcinfo); + if (tevent_req_nterror(req, status)) { + D_WARNING("wb_dsgetdcname_gencache_set() failed with %s.\n", + nt_errstr(status)); + return; + } + NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info); + + /* + * Note wb_sids2xids_send/recv was called before, + * so we're sure that wb_parent_idmap_setup_send/recv + * was already called. + */ + child_binding_handle = idmap_child_handle(); + subreq = dcerpc_wbint_GetNssInfo_send( + state, state->ev, child_binding_handle, info); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_done, req); +} + +static void wb_queryuser_got_gid(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_queryuser_state *state = tevent_req_data( + req, struct wb_queryuser_state); + struct unixid xid; + NTSTATUS status; + bool need_group_name = false; + const char *tmpl = NULL; + struct dom_sid_buf buf; + + status = wb_sids2xids_recv(subreq, &xid, 1); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("wb_sids2xids_recv() failed with %s.\n", + nt_errstr(status)); + return; + } + + D_DEBUG("Got XID %"PRIu32" with type %d.\n", xid.id, xid.type); + if ((xid.type != ID_TYPE_GID) && (xid.type != ID_TYPE_BOTH)) { + D_WARNING("Returning NT_STATUS_NO_SUCH_USER\n" + "xid.type must be ID_TYPE_UID or ID_TYPE_BOTH.\n"); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return; + } + + state->info->primary_gid = xid.id; + + tmpl = lp_template_homedir(); + if(strstr_m(tmpl, "%g") || strstr_m(tmpl, "%G")) { + need_group_name = true; + } + tmpl = lp_template_shell(); + if(strstr_m(tmpl, "%g") || strstr_m(tmpl, "%G")) { + need_group_name = true; + } + + NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info); + + if (need_group_name && state->info->primary_group_name == NULL) { + D_DEBUG("Calling wb_lookupsid_send for group SID %s.\n", + dom_sid_str_buf(&state->info->group_sid, &buf)); + subreq = wb_lookupsid_send(state, state->ev, + &state->info->group_sid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_queryuser_got_group_name, + req); + return; + } + + D_DEBUG("No need to lookup primary group name. Request is done!\n"); + tevent_req_done(req); +} + +static void wb_queryuser_got_group_name(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_queryuser_state *state = tevent_req_data( + req, struct wb_queryuser_state); + enum lsa_SidType type; + NTSTATUS status; + const char *domain_name; + + status = wb_lookupsid_recv(subreq, state->info, &type, &domain_name, + &state->info->primary_group_name); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("wb_lookupsid_recv() failed with %s.\n", + nt_errstr(status)); + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_queryuser_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct wbint_userinfo **pinfo) +{ + struct wb_queryuser_state *state = tevent_req_data( + req, struct wb_queryuser_state); + NTSTATUS status; + + D_INFO("WB command queryuser end.\n"); + NDR_PRINT_DEBUG_LEVEL(DBGLVL_INFO, wbint_userinfo, state->info); + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *pinfo = talloc_move(mem_ctx, &state->info); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_seqnum.c b/source3/winbindd/wb_seqnum.c new file mode 100644 index 0000000..7affd76 --- /dev/null +++ b/source3/winbindd/wb_seqnum.c @@ -0,0 +1,78 @@ +/* + Unix SMB/CIFS implementation. + async seqnum + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct wb_seqnum_state { + uint32_t seqnum; +}; + +static void wb_seqnum_done(struct tevent_req *subreq); + +struct tevent_req *wb_seqnum_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain) +{ + struct tevent_req *req, *subreq; + struct wb_seqnum_state *state; + + req = tevent_req_create(mem_ctx, &state, struct wb_seqnum_state); + if (req == NULL) { + return NULL; + } + subreq = dcerpc_wbint_QuerySequenceNumber_send( + state, ev, dom_child_handle(domain), &state->seqnum); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_seqnum_done, req); + return req; +} + +static void wb_seqnum_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_seqnum_state *state = tevent_req_data( + req, struct wb_seqnum_state); + NTSTATUS status, result; + + status = dcerpc_wbint_QuerySequenceNumber_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS wb_seqnum_recv(struct tevent_req *req, uint32_t *seqnum) +{ + struct wb_seqnum_state *state = tevent_req_data( + req, struct wb_seqnum_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *seqnum = state->seqnum; + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_seqnums.c b/source3/winbindd/wb_seqnums.c new file mode 100644 index 0000000..24955ad --- /dev/null +++ b/source3/winbindd/wb_seqnums.c @@ -0,0 +1,153 @@ +/* + Unix SMB/CIFS implementation. + + async seqnums, update the seqnums in winbindd_cache.c + + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct wb_seqnums_state { + int num_domains; + int num_received; + + struct tevent_req **subreqs; + struct winbindd_domain **domains; + NTSTATUS *statuses; + uint32_t *seqnums; +}; + +static void wb_seqnums_done(struct tevent_req *subreq); + +struct tevent_req *wb_seqnums_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev) +{ + struct tevent_req *req; + struct wb_seqnums_state *state; + struct winbindd_domain *domain; + int i; + + req = tevent_req_create(mem_ctx, &state, struct wb_seqnums_state); + if (req == NULL) { + return NULL; + } + state->num_received = 0; + state->num_domains = 0; + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + state->num_domains += 1; + } + + state->subreqs = talloc_array(state, struct tevent_req *, + state->num_domains); + state->domains = talloc_zero_array(state, struct winbindd_domain *, + state->num_domains); + state->statuses = talloc_array(state, NTSTATUS, state->num_domains); + state->seqnums = talloc_array(state, uint32_t, state->num_domains); + + if ((state->subreqs == NULL) || (state->domains == NULL) || + (state->statuses == NULL) || (state->seqnums == NULL)) { + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return tevent_req_post(req, ev); + } + + i = 0; + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + state->domains[i] = domain; + state->subreqs[i] = wb_seqnum_send(state->subreqs, ev, domain); + if (tevent_req_nomem(state->subreqs[i], req)) { + /* Don't even start all the other requests */ + TALLOC_FREE(state->subreqs); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(state->subreqs[i], wb_seqnums_done, + req); + i += 1; + } + return req; +} + +static void wb_seqnums_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_seqnums_state *state = tevent_req_data( + req, struct wb_seqnums_state); + NTSTATUS status; + uint32_t seqnum; + int i; + + status = wb_seqnum_recv(subreq, &seqnum); + + for (i=0; i<state->num_domains; i++) { + if (subreq == state->subreqs[i]) { + break; + } + } + if (i < state->num_domains) { + /* found one */ + + state->subreqs[i] = NULL; + state->statuses[i] = status; + if (NT_STATUS_IS_OK(status)) { + state->seqnums[i] = seqnum; + + /* + * This first assignment might be removed + * later + */ + state->domains[i]->sequence_number = seqnum; + + if (!wcache_store_seqnum(state->domains[i]->name, + state->seqnums[i], + time(NULL))) { + DEBUG(1, ("wcache_store_seqnum failed for " + "domain %s\n", + state->domains[i]->name)); + } + } + } + + TALLOC_FREE(subreq); + + state->num_received += 1; + + if (state->num_received >= state->num_domains) { + tevent_req_done(req); + } +} + +NTSTATUS wb_seqnums_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + int *num_domains, struct winbindd_domain ***domains, + NTSTATUS **statuses, uint32_t **seqnums) +{ + struct wb_seqnums_state *state = tevent_req_data( + req, struct wb_seqnums_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *num_domains = state->num_domains; + *domains = talloc_move(mem_ctx, &state->domains); + *statuses = talloc_move(mem_ctx, &state->statuses); + *seqnums = talloc_move(mem_ctx, &state->seqnums); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_sids2xids.c b/source3/winbindd/wb_sids2xids.c new file mode 100644 index 0000000..f0f6c23 --- /dev/null +++ b/source3/winbindd/wb_sids2xids.c @@ -0,0 +1,786 @@ +/* + Unix SMB/CIFS implementation. + async sids2xids + Copyright (C) Volker Lendecke 2011 + Copyright (C) Michael Adam 2012 + + 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.h" +#include "../libcli/security/security.h" +#include "idmap_cache.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "lsa.h" + +struct wb_sids2xids_state { + struct tevent_context *ev; + + const struct wb_parent_idmap_config *cfg; + + struct dom_sid *sids; + uint32_t num_sids; + + struct wbint_TransIDArray all_ids; + + /* Used to translated the idx back into all_ids.ids[idx] */ + uint32_t *tmp_idx; + + uint32_t lookup_count; + struct dom_sid *lookup_sids; + + struct wbint_TransIDArray map_ids_in; + struct wbint_TransIDArray map_ids_out; + + /* + * Domain array to use for the idmap call. The output from + * lookupsids cannot be used directly since for migrated + * objects the returned domain SID can be different than the + * original one. The new domain SID cannot be combined with + * the RID from the previous domain. + * + * The proper way would be asking for the correct RID in the + * new domain, but this approach avoids id mappings for + * invalid SIDs. + */ + struct lsa_RefDomainList idmap_doms; + + uint32_t dom_index; + struct lsa_RefDomainList idmap_dom; + bool tried_dclookup; +}; + +static void wb_sids2xids_idmap_setup_done(struct tevent_req *subreq); +static bool wb_sids2xids_in_cache(struct dom_sid *sid, struct id_map *map); +static void wb_sids2xids_lookupsids_done(struct tevent_req *subreq); +static void wb_sids2xids_done(struct tevent_req *subreq); +static void wb_sids2xids_gotdc(struct tevent_req *subreq); +static void wb_sids2xids_next_sids2unix(struct tevent_req *req); +static enum id_type lsa_SidType_to_id_type(const enum lsa_SidType sid_type); + +struct tevent_req *wb_sids2xids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sids, + const uint32_t num_sids) +{ + struct tevent_req *req, *subreq; + struct wb_sids2xids_state *state; + uint32_t i; + uint32_t num_valid = 0; + + req = tevent_req_create(mem_ctx, &state, + struct wb_sids2xids_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command sids2xids start.\n" + "Resolving %"PRIu32" SID(s).\n", num_sids); + + state->ev = ev; + + state->num_sids = num_sids; + + state->sids = talloc_zero_array(state, struct dom_sid, num_sids); + if (tevent_req_nomem(state->sids, req)) { + return tevent_req_post(req, ev); + } + + for (i = 0; i < num_sids; i++) { + sid_copy(&state->sids[i], &sids[i]); + } + + state->all_ids.num_ids = num_sids; + state->all_ids.ids = talloc_zero_array(state, struct wbint_TransID, num_sids); + if (tevent_req_nomem(state->all_ids.ids, req)) { + return tevent_req_post(req, ev); + } + + state->tmp_idx = talloc_zero_array(state, uint32_t, num_sids); + if (tevent_req_nomem(state->tmp_idx, req)) { + return tevent_req_post(req, ev); + } + + state->lookup_sids = talloc_zero_array(state, struct dom_sid, num_sids); + if (tevent_req_nomem(state->lookup_sids, req)) { + return tevent_req_post(req, ev); + } + + state->map_ids_in.ids = talloc_zero_array(state, struct wbint_TransID, num_sids); + if (tevent_req_nomem(state->map_ids_in.ids, req)) { + return tevent_req_post(req, ev); + } + + /* + * Extract those sids that can not be resolved from cache + * into a separate list to be handed to id mapping, keeping + * the same index. + */ + for (i=0; i<state->num_sids; i++) { + struct wbint_TransID *cur_id = &state->all_ids.ids[i]; + struct dom_sid domain_sid; + struct dom_sid_buf buf; + struct id_map map = { .status = ID_UNMAPPED, }; + uint32_t rid = 0; + bool in_cache; + + sid_copy(&domain_sid, &state->sids[i]); + sid_split_rid(&domain_sid, &rid); + + /* + * Start with an invalid entry. + */ + *cur_id = (struct wbint_TransID) { + .type_hint = ID_TYPE_NOT_SPECIFIED, + .domain_index = UINT32_MAX - 1, /* invalid */ + .rid = rid, + .xid = { + .id = UINT32_MAX, + .type = ID_TYPE_NOT_SPECIFIED, + }, + }; + + D_DEBUG("%"PRIu32": SID %s\n", + i, dom_sid_str_buf(&state->sids[i], &buf)); + + in_cache = wb_sids2xids_in_cache(&state->sids[i], &map); + if (in_cache) { + /* + * We used to ignore map.status and just rely + * on map.xid.type. + * + * Lets keep this logic for now... + */ + + cur_id->xid = map.xid; + cur_id->domain_index = UINT32_MAX; /* this marks it as filled entry */ + num_valid += 1; + continue; + } + } + + D_DEBUG("Found %"PRIu32" (out of %"PRIu32") SID(s) in cache.\n", + num_valid, num_sids); + if (num_valid == num_sids) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = wb_parent_idmap_setup_send(state, state->ev); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_sids2xids_idmap_setup_done, req); + return req; +} + +static void wb_sids2xids_idmap_setup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_sids2xids_state *state = tevent_req_data( + req, struct wb_sids2xids_state); + NTSTATUS status; + uint32_t i; + + status = wb_parent_idmap_setup_recv(subreq, &state->cfg); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return; + } + SMB_ASSERT(state->cfg->num_doms > 0); + D_DEBUG("We will loop over %"PRIu32" SID(s) (skipping those already resolved via cache) and over %"PRIu32" domain(s).\n", + state->num_sids, + state->cfg->num_doms); + + /* + * Now we build a list with all domain + * with non cached entries + */ + for (i=0; i<state->num_sids; i++) { + struct wbint_TransID *t = &state->all_ids.ids[i]; + struct dom_sid domain_sid; + const char *domain_name = NULL; + int domain_index; + uint32_t rid = 0; + uint32_t di; + struct dom_sid_buf buf0, buf1; + + D_DEBUG("%"PRIu32": Processing SID %s\n", + i, + dom_sid_str_buf(&state->sids[i], &buf0)); + if (t->domain_index == UINT32_MAX) { + /* ignore already filled entries */ + D_DEBUG("%"PRIu32": Ignoring already resolved SID %s\n", + i, + dom_sid_str_buf(&state->sids[i], &buf0)); + continue; + } + + sid_copy(&domain_sid, &state->sids[i]); + sid_split_rid(&domain_sid, &rid); + D_DEBUG("%"PRIu32": Split SID %s into domain SID %s and RID %"PRIu32"\n", + i, + dom_sid_str_buf(&state->sids[i], &buf0), + dom_sid_str_buf(&domain_sid, &buf1), + rid); + + if (t->type_hint == ID_TYPE_NOT_SPECIFIED) { + const char *tmp_name = NULL; + enum lsa_SidType sid_type = SID_NAME_USE_NONE; + const struct dom_sid *tmp_authority_sid = NULL; + const char *tmp_authority_name = NULL; + + /* + * Try to get a type hint from for predefined sids + */ + status = dom_sid_lookup_predefined_sid(&state->sids[i], + &tmp_name, + &sid_type, + &tmp_authority_sid, + &tmp_authority_name); + if (NT_STATUS_IS_OK(status)) { + t->type_hint = lsa_SidType_to_id_type(sid_type); + D_DEBUG("Got a type hint: %d from predefined SID.\n", + t->type_hint); + } + } + + D_DEBUG("Looping over %"PRIu32" domain(s) to find domain SID %s.\n", + state->cfg->num_doms, + dom_sid_str_buf(&domain_sid, &buf0)); + for (di = 0; di < state->cfg->num_doms; di++) { + struct wb_parent_idmap_config_dom *dom = + &state->cfg->doms[di]; + bool match; + + match = dom_sid_equal(&domain_sid, &dom->sid); + if (!match) { + continue; + } + + domain_name = dom->name; + D_DEBUG("Found domain '%s'.\n", domain_name); + break; + } + if (domain_name == NULL) { + struct winbindd_domain *wb_domain = NULL; + + D_DEBUG("Could not find a domain for domain SID %s. Trying to fill the domain name from list of known domains.\n", + dom_sid_str_buf(&domain_sid, &buf0)); + /* + * Try to fill the name if we already know it + */ + wb_domain = find_domain_from_sid_noinit(&state->sids[i]); + if (wb_domain != NULL) { + domain_name = wb_domain->name; + D_DEBUG("Found domain '%s' in list of known domains.\n", domain_name); + } + } + if (domain_name == NULL) { + domain_name = ""; + D_DEBUG("Not found domain in list of known domains, setting empty domain name.\n"); + } + + if (t->type_hint == ID_TYPE_NOT_SPECIFIED) { + if (domain_name[0] != '\0') { + /* + * We know the domain, we indicate this + * by passing ID_TYPE_BOTH as a hint + * + * Maybe that's already enough for the backend + */ + t->type_hint = ID_TYPE_BOTH; + D_DEBUG("Setting type hint ID_TYPE_BOTH for domain '%s'.\n", domain_name); + } + } + + domain_index = init_lsa_ref_domain_list(state, + &state->idmap_doms, + domain_name, + &domain_sid); + if (domain_index == -1) { + tevent_req_oom(req); + return; + } + t->domain_index = domain_index; + } + + /* + * We defer lookupsids because it requires domain controller + * interaction. + * + * First we ask the idmap child without explicit type hints. + * In most cases mappings already exist in the backend and + * a type_hint is not needed. + */ + wb_sids2xids_next_sids2unix(req); +} + +static bool wb_sids2xids_in_cache(struct dom_sid *sid, struct id_map *map) +{ + struct unixid id; + bool expired; + + if (!winbindd_use_idmap_cache()) { + return false; + } + if (idmap_cache_find_sid2unixid(sid, &id, &expired)) { + if (expired && is_domain_online(find_our_domain())) { + return false; + } + map->sid = sid; + map->xid = id; + map->status = ID_MAPPED; + return true; + } + return false; +} + +static void wb_sids2xids_lookupsids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_sids2xids_state *state = tevent_req_data( + req, struct wb_sids2xids_state); + struct lsa_RefDomainList *domains = NULL; + struct lsa_TransNameArray *names = NULL; + NTSTATUS status; + uint32_t li; + + status = wb_lookupsids_recv(subreq, state, &domains, &names); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return; + } + + if (domains == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n"); + return; + } + + if (names == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n"); + return; + } + + for (li = 0; li < state->lookup_count; li++) { + struct lsa_TranslatedName *n = &names->names[li]; + uint32_t ai = state->tmp_idx[li]; + struct wbint_TransID *t = &state->all_ids.ids[ai]; + enum id_type type_hint; + + type_hint = lsa_SidType_to_id_type(n->sid_type); + if (type_hint != ID_TYPE_NOT_SPECIFIED) { + /* + * We know it's a valid user or group. + */ + t->type_hint = type_hint; + continue; + } + + if (n->sid_index == UINT32_MAX) { + /* + * The domain is not known, there's + * no point to try mapping again. + * mark is done and add a negative cache + * entry. + */ + t->domain_index = UINT32_MAX; /* mark as valid */ + idmap_cache_set_sid2unixid(&state->sids[ai], &t->xid); + continue; + } + + if (n->sid_index >= domains->count) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n"); + return; + } + + if (domains->domains[n->sid_index].name.string == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n"); + return; + } + if (domains->domains[n->sid_index].sid == NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n"); + return; + } + + if (t->type_hint != ID_TYPE_NOT_SPECIFIED) { + /* + * We already tried with a type hint there's + * no point to try mapping again with ID_TYPE_BOTH. + * + * Mark is done and add a negative cache entry. + */ + t->domain_index = UINT32_MAX; /* mark as valid */ + idmap_cache_set_sid2unixid(&state->sids[ai], &t->xid); + continue; + } + + /* + * We only know the domain exists, but the user doesn't + */ + t->type_hint = ID_TYPE_BOTH; + } + + TALLOC_FREE(names); + TALLOC_FREE(domains); + + /* + * Now that we have type_hints for the remaining sids, + * we need to restart with the first domain. + */ + state->dom_index = 0; + wb_sids2xids_next_sids2unix(req); +} + +static void wb_sids2xids_next_sids2unix(struct tevent_req *req) +{ + struct wb_sids2xids_state *state = tevent_req_data( + req, struct wb_sids2xids_state); + struct tevent_req *subreq = NULL; + struct dcerpc_binding_handle *child_binding_handle = NULL; + const struct wbint_TransIDArray *src = NULL; + struct wbint_TransIDArray *dst = NULL; + uint32_t si; + + next_domain: + state->tried_dclookup = false; + + D_DEBUG("Processing next domain (dom_index=%"PRIu32", idmap_doms.count=%"PRIu32", lookup_count=%"PRIu32").\n", + state->dom_index, + state->idmap_doms.count, + state->lookup_count); + if (state->dom_index == state->idmap_doms.count) { + if (state->lookup_count != 0) { + /* + * We already called wb_lookupsids_send() + * before, so we're done. + */ + D_DEBUG("We already called wb_lookupsids_send() before, so we're done.\n"); + tevent_req_done(req); + return; + } + + for (si=0; si < state->num_sids; si++) { + struct wbint_TransID *t = &state->all_ids.ids[si]; + + if (t->domain_index == UINT32_MAX) { + /* ignore already filled entries */ + continue; + } + + state->tmp_idx[state->lookup_count] = si; + sid_copy(&state->lookup_sids[state->lookup_count], + &state->sids[si]); + state->lookup_count += 1; + } + + D_DEBUG("Prepared %"PRIu32" SID(s) for lookup wb_lookupsids_send().\n", + state->lookup_count); + if (state->lookup_count == 0) { + /* + * no wb_lookupsids_send() needed... + */ + tevent_req_done(req); + return; + } + + subreq = wb_lookupsids_send(state, + state->ev, + state->lookup_sids, + state->lookup_count); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_sids2xids_lookupsids_done, req); + return; + } + + src = &state->all_ids; + dst = &state->map_ids_in; + dst->num_ids = 0; + + for (si=0; si < src->num_ids; si++) { + if (src->ids[si].domain_index != state->dom_index) { + continue; + } + + state->tmp_idx[dst->num_ids] = si; + dst->ids[dst->num_ids] = src->ids[si]; + dst->ids[dst->num_ids].domain_index = 0; + dst->num_ids += 1; + } + + if (dst->num_ids == 0) { + state->dom_index += 1; + D_DEBUG("Go to next domain.\n"); + goto next_domain; + } + + state->idmap_dom = (struct lsa_RefDomainList) { + .count = 1, + .domains = &state->idmap_doms.domains[state->dom_index], + .max_size = 1 + }; + + /* + * dcerpc_wbint_Sids2UnixIDs_send/recv will + * allocate a new array for the response + * and overwrite _ids->ids pointer. + * + * So we better make a temporary copy + * of state->map_ids_in (which contains the request array) + * into state->map_ids_out. + * + * That makes it possible to reuse the pre-allocated + * state->map_ids_in.ids array. + */ + state->map_ids_out = state->map_ids_in; + child_binding_handle = idmap_child_handle(); + subreq = dcerpc_wbint_Sids2UnixIDs_send( + state, state->ev, child_binding_handle, &state->idmap_dom, + &state->map_ids_out); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_sids2xids_done, req); +} + +static enum id_type lsa_SidType_to_id_type(const enum lsa_SidType sid_type) +{ + enum id_type type; + + switch(sid_type) { + case SID_NAME_COMPUTER: + case SID_NAME_USER: + type = ID_TYPE_UID; + break; + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + type = ID_TYPE_GID; + break; + default: + type = ID_TYPE_NOT_SPECIFIED; + break; + } + + return type; +} + +static void wb_sids2xids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_sids2xids_state *state = tevent_req_data( + req, struct wb_sids2xids_state); + NTSTATUS status, result; + const struct wbint_TransIDArray *src = NULL; + struct wbint_TransIDArray *dst = NULL; + uint32_t si; + + status = dcerpc_wbint_Sids2UnixIDs_recv(subreq, state, &result); + TALLOC_FREE(subreq); + + if (tevent_req_nterror(req, status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return; + } + + if (NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) && + !state->tried_dclookup) { + + struct lsa_DomainInfo *d; + + D_DEBUG("Domain controller not found. Calling wb_dsgetdcname_send() to get it.\n"); + d = &state->idmap_doms.domains[state->dom_index]; + + subreq = wb_dsgetdcname_send( + state, state->ev, d->name.string, NULL, NULL, + DS_RETURN_DNS_NAME); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_sids2xids_gotdc, req); + return; + } + + src = &state->map_ids_out; + dst = &state->all_ids; + + if (any_nt_status_not_ok(status, result, &status)) { + D_DEBUG("Either status %s or result %s is not ok. Report SIDs as not mapped.\n", + nt_errstr(status), + nt_errstr(result)); + /* + * All we can do here is to report "not mapped" + */ + src = &state->map_ids_in; + for (si=0; si < src->num_ids; si++) { + src->ids[si].xid.type = ID_TYPE_NOT_SPECIFIED; + } + } + + if (src->num_ids != state->map_ids_in.num_ids) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + D_WARNING("Number of mapped SIDs does not match. Failing with NT_STATUS_INTERNAL_ERROR.\n"); + return; + } + + for (si=0; si < src->num_ids; si++) { + uint32_t di = state->tmp_idx[si]; + + if (src->ids[si].xid.type == ID_TYPE_WB_REQUIRE_TYPE) { + if (state->lookup_count == 0) { + D_DEBUG("The backend asks for more information (a type_hint), we'll do a lookupsids later.\n"); + /* + * The backend asks for more information + * (a type_hint), we'll do a lookupsids + * later. + */ + continue; + } + + /* + * lookupsids was not able to provide a type_hint that + * satisfied the backend. + * + * Make sure we don't expose ID_TYPE_WB_REQUIRE_TYPE + * outside of winbindd! + */ + D_DEBUG("lookupsids was not able to provide a type_hint that satisfied the backend. Make sure we don't expose ID_TYPE_WB_REQUIRE_TYPE outside of winbindd!\n"); + src->ids[si].xid.type = ID_TYPE_NOT_SPECIFIED; + } + + if (src->ids[si].xid.type != ID_TYPE_NOT_SPECIFIED) { + dst->ids[di].xid = src->ids[si].xid; + D_DEBUG("%"PRIu32": Setting XID %"PRIu32"\n", + si, src->ids[si].xid.id); + } + dst->ids[di].domain_index = UINT32_MAX; /* mark as valid */ + idmap_cache_set_sid2unixid(&state->sids[di], &dst->ids[di].xid); + } + + state->map_ids_in.num_ids = 0; + if (NT_STATUS_IS_OK(status)) { + /* + * If we got a valid response, we expect + * state->map_ids_out.ids to be a new allocated + * array, which we want to free early. + */ + SMB_ASSERT(state->map_ids_out.ids != state->map_ids_in.ids); + TALLOC_FREE(state->map_ids_out.ids); + } + state->map_ids_out = (struct wbint_TransIDArray) { .num_ids = 0, }; + + state->dom_index += 1; + + wb_sids2xids_next_sids2unix(req); +} + +static void wb_sids2xids_gotdc(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_sids2xids_state *state = tevent_req_data( + req, struct wb_sids2xids_state); + struct dcerpc_binding_handle *child_binding_handle = NULL; + struct netr_DsRGetDCNameInfo *dcinfo; + NTSTATUS status; + + status = wb_dsgetdcname_recv(subreq, state, &dcinfo); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return; + } + + state->tried_dclookup = true; + + { + struct lsa_DomainInfo *d = + &state->idmap_doms.domains[state->dom_index]; + const char *dom_name = d->name.string; + + status = wb_dsgetdcname_gencache_set(dom_name, dcinfo); + if (tevent_req_nterror(req, status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return; + } + } + + /* + * dcerpc_wbint_Sids2UnixIDs_send/recv will + * allocate a new array for the response + * and overwrite _ids->ids pointer. + * + * So we better make a temporary copy + * of state->map_ids_in (which contains the request array) + * into state->map_ids_out. + * + * That makes it possible to reuse the pre-allocated + * state->map_ids_in.ids array. + */ + state->map_ids_out = state->map_ids_in; + child_binding_handle = idmap_child_handle(); + subreq = dcerpc_wbint_Sids2UnixIDs_send( + state, state->ev, child_binding_handle, &state->idmap_dom, + &state->map_ids_out); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_sids2xids_done, req); +} + +NTSTATUS wb_sids2xids_recv(struct tevent_req *req, + struct unixid xids[], uint32_t num_xids) +{ + struct wb_sids2xids_state *state = tevent_req_data( + req, struct wb_sids2xids_state); + NTSTATUS status; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + + if (num_xids != state->num_sids) { + D_WARNING("Error. We have resolved only %"PRIu32" XID(s), but caller asked for %"PRIu32".\n", + state->num_sids, num_xids); + return NT_STATUS_INTERNAL_ERROR; + } + + D_INFO("WB command sids2xids end.\n"); + for (i=0; i<state->num_sids; i++) { + struct dom_sid_buf buf; + xids[i] = state->all_ids.ids[i].xid; + D_INFO("%"PRIu32": Found XID %"PRIu32" for SID %s\n", + i, + xids[i].id, + dom_sid_str_buf(&state->sids[i], &buf)); + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wb_xids2sids.c b/source3/winbindd/wb_xids2sids.c new file mode 100644 index 0000000..86bd7f9 --- /dev/null +++ b/source3/winbindd/wb_xids2sids.c @@ -0,0 +1,422 @@ +/* + * Unix SMB/CIFS implementation. + * async xids2sids + * Copyright (C) Volker Lendecke 2015 + * + * 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.h" +#include "../libcli/security/security.h" +#include "idmap_cache.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "passdb/lookup_sid.h" + +struct wb_xids2sids_dom_state { + struct tevent_context *ev; + struct unixid *all_xids; + const bool *cached; + size_t num_all_xids; + struct dom_sid *all_sids; + const struct wb_parent_idmap_config_dom *dom_map; + bool tried_dclookup; + + size_t num_dom_xids; + struct unixid *dom_xids; + struct dom_sid *dom_sids; +}; + +static void wb_xids2sids_dom_done(struct tevent_req *subreq); +static void wb_xids2sids_dom_gotdc(struct tevent_req *subreq); + +static struct tevent_req *wb_xids2sids_dom_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + const struct wb_parent_idmap_config_dom *dom_map, + struct unixid *xids, + const bool *cached, + size_t num_xids, + struct dom_sid *sids) +{ + struct tevent_req *req, *subreq; + struct wb_xids2sids_dom_state *state; + struct dcerpc_binding_handle *child_binding_handle = NULL; + size_t i; + + req = tevent_req_create(mem_ctx, &state, + struct wb_xids2sids_dom_state); + if (req == NULL) { + return NULL; + } + + D_DEBUG("Searching for %zu xid(s) in domain %s.\n", + num_xids, + dom_map->name); + + state->ev = ev; + state->all_xids = xids; + state->cached = cached; + state->num_all_xids = num_xids; + state->all_sids = sids; + state->dom_map = dom_map; + + state->dom_xids = talloc_array(state, struct unixid, num_xids); + if (tevent_req_nomem(state->dom_xids, req)) { + return tevent_req_post(req, ev); + } + state->dom_sids = talloc_array(state, struct dom_sid, num_xids); + if (tevent_req_nomem(state->dom_sids, req)) { + return tevent_req_post(req, ev); + } + + for (i=0; i<num_xids; i++) { + struct unixid id = state->all_xids[i]; + + if ((id.id < dom_map->low_id) || (id.id > dom_map->high_id)) { + /* out of range */ + D_DEBUG("%zu: XID %"PRIu32" is out of range.\n", + i, id.id); + continue; + } + if (state->cached[i]) { + /* already found in cache */ + D_DEBUG("%zu: XID %"PRIu32" is already found in cache.\n", + i, id.id); + continue; + } + if (!is_null_sid(&state->all_sids[i])) { + /* already mapped in a previously asked domain */ + D_DEBUG("%zu: XID %"PRIu32" is already mapped in a previously asked domain.\n", + i, id.id); + continue; + } + D_DEBUG("%zu: XID %"PRIu32" will be looked up via dcerpc_wbint_UnixIDs2Sids_send().\n", + i, id.id); + state->dom_xids[state->num_dom_xids++] = id; + } + + if (state->num_dom_xids == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + child_binding_handle = idmap_child_handle(); + subreq = dcerpc_wbint_UnixIDs2Sids_send( + state, ev, child_binding_handle, dom_map->name, dom_map->sid, + state->num_dom_xids, state->dom_xids, state->dom_sids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_xids2sids_dom_done, req); + return req; +} + +static void wb_xids2sids_dom_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_xids2sids_dom_state *state = tevent_req_data( + req, struct wb_xids2sids_dom_state); + const struct wb_parent_idmap_config_dom *dom_map = state->dom_map; + NTSTATUS status, result; + size_t i; + size_t dom_sid_idx; + + status = dcerpc_wbint_UnixIDs2Sids_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) && + !state->tried_dclookup) { + + subreq = wb_dsgetdcname_send( + state, state->ev, state->dom_map->name, NULL, NULL, + DS_RETURN_DNS_NAME); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_xids2sids_dom_gotdc, req); + return; + } + + if (!NT_STATUS_EQUAL(result, NT_STATUS_NONE_MAPPED) && + tevent_req_nterror(req, result)) { + return; + } + + dom_sid_idx = 0; + + D_DEBUG("Processing response for %zu xid(s).\n", state->num_all_xids); + for (i=0; i<state->num_all_xids; i++) { + struct unixid *id = &state->all_xids[i]; + struct dom_sid_buf buf; + + if ((id->id < dom_map->low_id) || (id->id > dom_map->high_id)) { + /* out of range */ + continue; + } + if (state->cached[i]) { + /* already found in cache */ + continue; + } + if (!is_null_sid(&state->all_sids[i])) { + /* already mapped in a previously asked domain */ + continue; + } + + sid_copy(&state->all_sids[i], &state->dom_sids[dom_sid_idx]); + *id = state->dom_xids[dom_sid_idx]; + D_DEBUG("%zu: XID %"PRIu32" mapped to SID %s.\n", + i, + id->id, + dom_sid_str_buf(&state->all_sids[i], &buf)); + + dom_sid_idx += 1; + } + + tevent_req_done(req); +} + +static void wb_xids2sids_dom_gotdc(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_xids2sids_dom_state *state = tevent_req_data( + req, struct wb_xids2sids_dom_state); + struct dcerpc_binding_handle *child_binding_handle = NULL; + struct netr_DsRGetDCNameInfo *dcinfo; + NTSTATUS status; + + status = wb_dsgetdcname_recv(subreq, state, &dcinfo); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->tried_dclookup = true; + + status = wb_dsgetdcname_gencache_set(state->dom_map->name, dcinfo); + if (tevent_req_nterror(req, status)) { + return; + } + + child_binding_handle = idmap_child_handle(); + subreq = dcerpc_wbint_UnixIDs2Sids_send( + state, state->ev, child_binding_handle, state->dom_map->name, + state->dom_map->sid, state->num_dom_xids, + state->dom_xids, state->dom_sids); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_xids2sids_dom_done, req); +} + +static NTSTATUS wb_xids2sids_dom_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +struct wb_xids2sids_state { + struct tevent_context *ev; + struct unixid *xids; + size_t num_xids; + struct dom_sid *sids; + bool *cached; + + size_t dom_idx; + const struct wb_parent_idmap_config *cfg; +}; + +static void wb_xids2sids_idmap_setup_done(struct tevent_req *subreq); +static void wb_xids2sids_done(struct tevent_req *subreq); + +struct tevent_req *wb_xids2sids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct unixid *xids, + uint32_t num_xids) +{ + struct tevent_req *req, *subreq; + struct wb_xids2sids_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct wb_xids2sids_state); + if (req == NULL) { + return NULL; + } + + D_INFO("WB command xids2sids start.\nLooking up %"PRIu32" XID(s).\n", + num_xids); + + state->ev = ev; + state->num_xids = num_xids; + + state->xids = talloc_array(state, struct unixid, num_xids); + if (tevent_req_nomem(state->xids, req)) { + return tevent_req_post(req, ev); + } + memcpy(state->xids, xids, num_xids * sizeof(struct unixid)); + + state->sids = talloc_zero_array(state, struct dom_sid, num_xids); + if (tevent_req_nomem(state->sids, req)) { + return tevent_req_post(req, ev); + } + + state->cached = talloc_zero_array(state, bool, num_xids); + if (tevent_req_nomem(state->cached, req)) { + return tevent_req_post(req, ev); + } + + if (winbindd_use_idmap_cache()) { + uint32_t i; + + for (i=0; i<num_xids; i++) { + struct dom_sid sid = {0}; + bool ok, expired = true; + + ok = idmap_cache_find_xid2sid( + &xids[i], &sid, &expired); + if (ok && !expired) { + struct dom_sid_buf buf; + DBG_DEBUG("Found %cID in cache: %s\n", + xids[i].type == ID_TYPE_UID?'U':'G', + dom_sid_str_buf(&sid, &buf)); + + sid_copy(&state->sids[i], &sid); + state->cached[i] = true; + } + } + } + + subreq = wb_parent_idmap_setup_send(state, state->ev); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_xids2sids_idmap_setup_done, req); + return req; +} + +static void wb_xids2sids_idmap_setup_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_xids2sids_state *state = tevent_req_data( + req, struct wb_xids2sids_state); + NTSTATUS status; + + status = wb_parent_idmap_setup_recv(subreq, &state->cfg); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + SMB_ASSERT(state->cfg->num_doms > 0); + + subreq = wb_xids2sids_dom_send( + state, state->ev, + &state->cfg->doms[state->dom_idx], + state->xids, state->cached, state->num_xids, state->sids); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_xids2sids_done, req); + return; +} + +static void wb_xids2sids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_xids2sids_state *state = tevent_req_data( + req, struct wb_xids2sids_state); + size_t i; + NTSTATUS status; + + status = wb_xids2sids_dom_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->dom_idx += 1; + if (state->dom_idx < state->cfg->num_doms) { + const struct wb_parent_idmap_config_dom *dom_map = + &state->cfg->doms[state->dom_idx]; + + subreq = wb_xids2sids_dom_send(state, + state->ev, + dom_map, + state->xids, + state->cached, + state->num_xids, + state->sids); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_xids2sids_done, req); + return; + } + + + for (i = 0; i < state->num_xids; i++) { + /* + * Prime the cache after an xid2sid call. It's important that we + * use the xid value returned from the backend for the xid value + * passed to idmap_cache_set_sid2unixid(), not the input to + * wb_xids2sids_send: the input carries what was asked for, + * e.g. a ID_TYPE_UID. The result from the backend something the + * idmap child possibly changed to ID_TYPE_BOTH. + * + * And of course If the value was from the cache don't update + * the cache. + */ + + if (state->cached[i]) { + continue; + } + + idmap_cache_set_sid2unixid(&state->sids[i], &state->xids[i]); + } + + tevent_req_done(req); + return; +} + +NTSTATUS wb_xids2sids_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct dom_sid **sids) +{ + struct wb_xids2sids_state *state = tevent_req_data( + req, struct wb_xids2sids_state); + NTSTATUS status; + size_t i; + + D_INFO("WB command xids2sids end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("wb_sids_to_xids failed: %s\n", nt_errstr(status)); + return status; + } + + *sids = talloc_move(mem_ctx, &state->sids); + if (CHECK_DEBUGLVL(DBGLVL_INFO)) { + for (i = 0; i < state->num_xids; i++) { + struct dom_sid_buf buf; + D_INFO("%zu: XID %"PRIu32" mapped to SID %s\n", + i, + state->xids[i].id, + dom_sid_str_buf(&((*sids)[i]), &buf)); + } + } + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd.c b/source3/winbindd/winbindd.c new file mode 100644 index 0000000..29a24a9 --- /dev/null +++ b/source3/winbindd/winbindd.c @@ -0,0 +1,1742 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) by Tim Potter 2000-2002 + Copyright (C) Andrew Tridgell 2002 + Copyright (C) Jelmer Vernooij 2003 + Copyright (C) Volker Lendecke 2004 + + 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 "lib/cmdline/cmdline.h" +#include "winbindd.h" +#include "nsswitch/winbind_client.h" +#include "nsswitch/wb_reqtrans.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_lsa_scompat.h" +#include "librpc/gen_ndr/ndr_samr_scompat.h" +#include "librpc/gen_ndr/ndr_winbind_scompat.h" +#include "secrets.h" +#include "rpc_client/cli_netlogon.h" +#include "idmap.h" +#include "lib/addrchange.h" +#include "auth.h" +#include "messages.h" +#include "../lib/util/pidfile.h" +#include "util_cluster.h" +#include "source4/lib/messaging/irpc.h" +#include "source4/lib/messaging/messaging.h" +#include "lib/param/param.h" +#include "lib/async_req/async_sock.h" +#include "libsmb/samlogon_cache.h" +#include "libcli/auth/netlogon_creds_cli.h" +#include "passdb.h" +#include "lib/util/tevent_req_profile.h" +#include "lib/gencache.h" +#include "rpc_server/rpc_config.h" +#include "lib/global_contexts.h" +#include "source3/lib/substitute.h" +#include "winbindd_traceid.h" +#include "lib/util/util_process.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +#define SCRUB_CLIENTS_INTERVAL 5 + +static bool client_is_idle(struct winbindd_cli_state *state); +static void remove_client(struct winbindd_cli_state *state); + +static bool interactive = False; + +/* Reload configuration */ + + + +static void winbindd_status(void) +{ + struct winbindd_cli_state *tmp; + + DEBUG(0, ("winbindd status:\n")); + + /* Print client state information */ + + DEBUG(0, ("\t%d clients currently active\n", winbindd_num_clients())); + + if (DEBUGLEVEL >= 2 && winbindd_num_clients()) { + DEBUG(2, ("\tclient list:\n")); + for(tmp = winbindd_client_list(); tmp; tmp = tmp->next) { + DEBUGADD(2, ("\t\tpid %lu, sock %d (%s)\n", + (unsigned long)tmp->pid, tmp->sock, + client_is_idle(tmp) ? "idle" : "active")); + } + } +} + +/* + handle stdin becoming readable when we are in --foreground mode + */ +static void winbindd_stdin_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + char c; + if (read(0, &c, 1) != 1) { + bool *is_parent = talloc_get_type_abort(private_data, bool); + + /* we have reached EOF on stdin, which means the + parent has exited. Shutdown the server */ + DEBUG(0,("EOF on stdin (is_parent=%d)\n", + (int)*is_parent)); + winbindd_terminate(*is_parent); + } +} + +bool winbindd_setup_stdin_handler(bool parent, bool foreground) +{ + bool *is_parent; + + if (foreground) { + struct stat st; + + is_parent = talloc(global_event_context(), bool); + if (!is_parent) { + return false; + } + + *is_parent = parent; + + /* if we are running in the foreground then look for + EOF on stdin, and exit if it happens. This allows + us to die if the parent process dies + Only do this on a pipe or socket, no other device. + */ + if (fstat(0, &st) != 0) { + return false; + } + if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) { + tevent_add_fd(global_event_context(), + is_parent, + 0, + TEVENT_FD_READ, + winbindd_stdin_handler, + is_parent); + } + } + + return true; +} + +static void winbindd_sig_chld_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + pid_t pid; + + while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { + winbind_child_died(pid); + } +} + +static bool winbindd_setup_sig_chld_handler(void) +{ + struct tevent_signal *se; + + se = tevent_add_signal(global_event_context(), + global_event_context(), + SIGCHLD, 0, + winbindd_sig_chld_handler, + NULL); + if (!se) { + return false; + } + + return true; +} + +static void winbindd_sig_usr2_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + winbindd_status(); +} + +static bool winbindd_setup_sig_usr2_handler(void) +{ + struct tevent_signal *se; + + se = tevent_add_signal(global_event_context(), + global_event_context(), + SIGUSR2, 0, + winbindd_sig_usr2_handler, + NULL); + if (!se) { + return false; + } + + return true; +} + +/* React on 'smbcontrol winbindd shutdown' in the same way as on SIGTERM*/ +static void msg_shutdown(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + /* only the parent waits for this message */ + DEBUG(0,("Got shutdown message\n")); + winbindd_terminate(true); +} + + +static void winbind_msg_validate_cache(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + uint8_t ret; + pid_t child_pid; + NTSTATUS status; + + DEBUG(10, ("winbindd_msg_validate_cache: got validate-cache " + "message.\n")); + + /* + * call the validation code from a child: + * so we don't block the main winbindd and the validation + * code can safely use fork/waitpid... + */ + child_pid = fork(); + + if (child_pid == -1) { + DEBUG(1, ("winbind_msg_validate_cache: Could not fork: %s\n", + strerror(errno))); + return; + } + + if (child_pid != 0) { + /* parent */ + DEBUG(5, ("winbind_msg_validate_cache: child created with " + "pid %d.\n", (int)child_pid)); + return; + } + + /* child */ + + status = winbindd_reinit_after_fork(NULL, NULL); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("winbindd_reinit_after_fork failed: %s\n", + nt_errstr(status))); + _exit(0); + } + + /* install default SIGCHLD handler: validation code uses fork/waitpid */ + CatchSignal(SIGCHLD, SIG_DFL); + + process_set_title("wb: check cache", "validate cache child"); + + ret = (uint8_t)winbindd_validate_cache_nobackup(); + DEBUG(10, ("winbindd_msg_validata_cache: got return value %d\n", ret)); + messaging_send_buf(msg_ctx, server_id, MSG_WINBIND_VALIDATE_CACHE, &ret, + (size_t)1); + _exit(0); +} + +static struct winbindd_bool_dispatch_table { + enum winbindd_cmd cmd; + bool (*fn)(struct winbindd_cli_state *state); + const char *cmd_name; +} bool_dispatch_table[] = { + { WINBINDD_INTERFACE_VERSION, + winbindd_interface_version, + "INTERFACE_VERSION" }, + { WINBINDD_INFO, + winbindd_info, + "INFO" }, + { WINBINDD_PING, + winbindd_ping, + "PING" }, + { WINBINDD_DOMAIN_NAME, + winbindd_domain_name, + "DOMAIN_NAME" }, + { WINBINDD_NETBIOS_NAME, + winbindd_netbios_name, + "NETBIOS_NAME" }, + { WINBINDD_DC_INFO, + winbindd_dc_info, + "DC_INFO" }, + { WINBINDD_CCACHE_NTLMAUTH, + winbindd_ccache_ntlm_auth, + "NTLMAUTH" }, + { WINBINDD_CCACHE_SAVE, + winbindd_ccache_save, + "CCACHE_SAVE" }, + { WINBINDD_PRIV_PIPE_DIR, + winbindd_priv_pipe_dir, + "WINBINDD_PRIV_PIPE_DIR" }, + { WINBINDD_LIST_TRUSTDOM, + winbindd_list_trusted_domains, + "LIST_TRUSTDOM" }, +}; + +struct winbindd_async_dispatch_table { + enum winbindd_cmd cmd; + const char *cmd_name; + struct tevent_req *(*send_req)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); + NTSTATUS (*recv_req)(struct tevent_req *req, + struct winbindd_response *presp); +}; + +static struct winbindd_async_dispatch_table async_nonpriv_table[] = { + { WINBINDD_LOOKUPSID, "LOOKUPSID", + winbindd_lookupsid_send, winbindd_lookupsid_recv }, + { WINBINDD_LOOKUPSIDS, "LOOKUPSIDS", + winbindd_lookupsids_send, winbindd_lookupsids_recv }, + { WINBINDD_LOOKUPNAME, "LOOKUPNAME", + winbindd_lookupname_send, winbindd_lookupname_recv }, + { WINBINDD_SIDS_TO_XIDS, "SIDS_TO_XIDS", + winbindd_sids_to_xids_send, winbindd_sids_to_xids_recv }, + { WINBINDD_XIDS_TO_SIDS, "XIDS_TO_SIDS", + winbindd_xids_to_sids_send, winbindd_xids_to_sids_recv }, + { WINBINDD_GETPWSID, "GETPWSID", + winbindd_getpwsid_send, winbindd_getpwsid_recv }, + { WINBINDD_GETPWNAM, "GETPWNAM", + winbindd_getpwnam_send, winbindd_getpwnam_recv }, + { WINBINDD_GETPWUID, "GETPWUID", + winbindd_getpwuid_send, winbindd_getpwuid_recv }, + { WINBINDD_GETSIDALIASES, "GETSIDALIASES", + winbindd_getsidaliases_send, winbindd_getsidaliases_recv }, + { WINBINDD_GETUSERDOMGROUPS, "GETUSERDOMGROUPS", + winbindd_getuserdomgroups_send, winbindd_getuserdomgroups_recv }, + { WINBINDD_GETGROUPS, "GETGROUPS", + winbindd_getgroups_send, winbindd_getgroups_recv }, + { WINBINDD_SHOW_SEQUENCE, "SHOW_SEQUENCE", + winbindd_show_sequence_send, winbindd_show_sequence_recv }, + { WINBINDD_GETGRGID, "GETGRGID", + winbindd_getgrgid_send, winbindd_getgrgid_recv }, + { WINBINDD_GETGRNAM, "GETGRNAM", + winbindd_getgrnam_send, winbindd_getgrnam_recv }, + { WINBINDD_GETUSERSIDS, "GETUSERSIDS", + winbindd_getusersids_send, winbindd_getusersids_recv }, + { WINBINDD_LOOKUPRIDS, "LOOKUPRIDS", + winbindd_lookuprids_send, winbindd_lookuprids_recv }, + { WINBINDD_SETPWENT, "SETPWENT", + winbindd_setpwent_send, winbindd_setpwent_recv }, + { WINBINDD_GETPWENT, "GETPWENT", + winbindd_getpwent_send, winbindd_getpwent_recv }, + { WINBINDD_ENDPWENT, "ENDPWENT", + winbindd_endpwent_send, winbindd_endpwent_recv }, + { WINBINDD_DSGETDCNAME, "DSGETDCNAME", + winbindd_dsgetdcname_send, winbindd_dsgetdcname_recv }, + { WINBINDD_GETDCNAME, "GETDCNAME", + winbindd_getdcname_send, winbindd_getdcname_recv }, + { WINBINDD_SETGRENT, "SETGRENT", + winbindd_setgrent_send, winbindd_setgrent_recv }, + { WINBINDD_GETGRENT, "GETGRENT", + winbindd_getgrent_send, winbindd_getgrent_recv }, + { WINBINDD_ENDGRENT, "ENDGRENT", + winbindd_endgrent_send, winbindd_endgrent_recv }, + { WINBINDD_LIST_USERS, "LIST_USERS", + winbindd_list_users_send, winbindd_list_users_recv }, + { WINBINDD_LIST_GROUPS, "LIST_GROUPS", + winbindd_list_groups_send, winbindd_list_groups_recv }, + { WINBINDD_CHECK_MACHACC, "CHECK_MACHACC", + winbindd_check_machine_acct_send, winbindd_check_machine_acct_recv }, + { WINBINDD_PING_DC, "PING_DC", + winbindd_ping_dc_send, winbindd_ping_dc_recv }, + { WINBINDD_PAM_AUTH, "PAM_AUTH", + winbindd_pam_auth_send, winbindd_pam_auth_recv }, + { WINBINDD_PAM_LOGOFF, "PAM_LOGOFF", + winbindd_pam_logoff_send, winbindd_pam_logoff_recv }, + { WINBINDD_PAM_CHAUTHTOK, "PAM_CHAUTHTOK", + winbindd_pam_chauthtok_send, winbindd_pam_chauthtok_recv }, + { WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP, "PAM_CHNG_PSWD_AUTH_CRAP", + winbindd_pam_chng_pswd_auth_crap_send, + winbindd_pam_chng_pswd_auth_crap_recv }, + { WINBINDD_WINS_BYIP, "WINS_BYIP", + winbindd_wins_byip_send, winbindd_wins_byip_recv }, + { WINBINDD_WINS_BYNAME, "WINS_BYNAME", + winbindd_wins_byname_send, winbindd_wins_byname_recv }, + { WINBINDD_DOMAIN_INFO, "DOMAIN_INFO", + winbindd_domain_info_send, winbindd_domain_info_recv }, + + { 0, NULL, NULL, NULL } +}; + +static struct winbindd_async_dispatch_table async_priv_table[] = { + { WINBINDD_ALLOCATE_UID, "ALLOCATE_UID", + winbindd_allocate_uid_send, winbindd_allocate_uid_recv }, + { WINBINDD_ALLOCATE_GID, "ALLOCATE_GID", + winbindd_allocate_gid_send, winbindd_allocate_gid_recv }, + { WINBINDD_CHANGE_MACHACC, "CHANGE_MACHACC", + winbindd_change_machine_acct_send, winbindd_change_machine_acct_recv }, + { WINBINDD_PAM_AUTH_CRAP, "PAM_AUTH_CRAP", + winbindd_pam_auth_crap_send, winbindd_pam_auth_crap_recv }, + + { 0, NULL, NULL, NULL } +}; + +struct process_request_state { + struct winbindd_cli_state *cli_state; + struct tevent_context *ev; +}; + +static void process_request_done(struct tevent_req *subreq); +static void process_request_written(struct tevent_req *subreq); + +static struct tevent_req *process_request_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli_state) +{ + struct tevent_req *req, *subreq; + struct process_request_state *state; + struct winbindd_async_dispatch_table *atable; + enum winbindd_cmd cmd = cli_state->request->cmd; + size_t i; + bool ok; + static uint64_t request_index = 1; + + /* + * debug traceid values: + * 0 .. inactive + * 1 .. not processing a winbind request, but in other code (timers) + * >=2 .. winbind request processing + */ + if (debug_traceid_get() != 0) { + request_index = ++request_index == 0 ? 2 : request_index; + debug_traceid_set(request_index); + } + req = tevent_req_create(mem_ctx, &state, + struct process_request_state); + if (req == NULL) { + return NULL; + } + state->cli_state = cli_state; + state->ev = ev; + + ok = tevent_req_set_profile(req); + if (!ok) { + return tevent_req_post(req, ev); + } + + SMB_ASSERT(cli_state->mem_ctx == NULL); + cli_state->mem_ctx = talloc_named(cli_state, 0, "winbind request"); + if (tevent_req_nomem(cli_state->mem_ctx, req)) { + return tevent_req_post(req, ev); + } + + cli_state->response = talloc_zero( + cli_state->mem_ctx, + struct winbindd_response); + if (tevent_req_nomem(cli_state->response, req)) { + return tevent_req_post(req, ev); + } + cli_state->response->result = WINBINDD_PENDING; + cli_state->response->length = sizeof(struct winbindd_response); + + /* Remember who asked us. */ + cli_state->pid = cli_state->request->pid; + memcpy(cli_state->client_name, + cli_state->request->client_name, + sizeof(cli_state->client_name)); + + cli_state->cmd_name = "unknown request"; + cli_state->recv_fn = NULL; + + /* client is newest */ + winbindd_promote_client(cli_state); + + for (atable = async_nonpriv_table; atable->send_req; atable += 1) { + if (cmd == atable->cmd) { + break; + } + } + + if ((atable->send_req == NULL) && cli_state->privileged) { + for (atable = async_priv_table; atable->send_req; + atable += 1) { + if (cmd == atable->cmd) { + break; + } + } + } + + if (atable->send_req != NULL) { + cli_state->cmd_name = atable->cmd_name; + cli_state->recv_fn = atable->recv_req; + + DBG_NOTICE("[%s (%d)] Handling async request: %s\n", + cli_state->client_name, + (int)cli_state->pid, + cli_state->cmd_name); + + subreq = atable->send_req( + state, + state->ev, + cli_state, + cli_state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, process_request_done, req); + return req; + } + + for (i=0; i<ARRAY_SIZE(bool_dispatch_table); i++) { + if (cmd == bool_dispatch_table[i].cmd) { + break; + } + } + + ok = false; + + if (i < ARRAY_SIZE(bool_dispatch_table)) { + cli_state->cmd_name = bool_dispatch_table[i].cmd_name; + + DBG_DEBUG("process_request: request fn %s\n", + bool_dispatch_table[i].cmd_name); + ok = bool_dispatch_table[i].fn(cli_state); + } + + cli_state->response->result = ok ? WINBINDD_OK : WINBINDD_ERROR; + + TALLOC_FREE(cli_state->io_req); + TALLOC_FREE(cli_state->request); + + subreq = wb_resp_write_send( + state, + state->ev, + cli_state->out_queue, + cli_state->sock, + cli_state->response); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, process_request_written, req); + + cli_state->io_req = subreq; + + return req; +} + +static void process_request_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct process_request_state *state = tevent_req_data( + req, struct process_request_state); + struct winbindd_cli_state *cli_state = state->cli_state; + NTSTATUS status; + bool ok; + + status = cli_state->recv_fn(subreq, cli_state->response); + TALLOC_FREE(subreq); + + DBG_NOTICE("[%s(%d):%s]: %s\n", + cli_state->client_name, + (int)cli_state->pid, + cli_state->cmd_name, + nt_errstr(status)); + + ok = NT_STATUS_IS_OK(status); + cli_state->response->result = ok ? WINBINDD_OK : WINBINDD_ERROR; + + TALLOC_FREE(cli_state->io_req); + TALLOC_FREE(cli_state->request); + + subreq = wb_resp_write_send( + state, + state->ev, + cli_state->out_queue, + cli_state->sock, + cli_state->response); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, process_request_written, req); + + cli_state->io_req = subreq; +} + +static void process_request_written(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct process_request_state *state = tevent_req_data( + req, struct process_request_state); + struct winbindd_cli_state *cli_state = state->cli_state; + ssize_t ret; + int err; + + cli_state->io_req = NULL; + + ret = wb_resp_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_nterror(req, map_nt_error_from_unix(err)); + return; + } + + DBG_DEBUG("[%s(%d):%s]: delivered response to client\n", + cli_state->client_name, + (int)cli_state->pid, + cli_state->cmd_name); + + TALLOC_FREE(cli_state->mem_ctx); + cli_state->response = NULL; + cli_state->cmd_name = "no request"; + cli_state->recv_fn = NULL; + + tevent_req_done(req); +} + +static NTSTATUS process_request_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct tevent_req_profile **profile) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *profile = tevent_req_move_profile(req, mem_ctx); + tevent_req_received(req); + return NT_STATUS_OK; +} + +/* + * This is the main event loop of winbind requests. It goes through a + * state-machine of 3 read/write requests, 4 if you have extra data to send. + * + * An idle winbind client has a read request of 4 bytes outstanding, + * finalizing function is request_len_recv, checking the length. request_recv + * then processes the packet. The processing function then at some point has + * to call request_finished which schedules sending the response. + */ + +static void winbind_client_request_read(struct tevent_req *req); +static void winbind_client_activity(struct tevent_req *req); +static void winbind_client_processed(struct tevent_req *req); + +/* Process a new connection by adding it to the client connection list */ + +static void new_connection(int listen_sock, bool privileged) +{ + struct sockaddr_un sunaddr; + struct winbindd_cli_state *state; + struct tevent_req *req; + socklen_t len; + int sock; + + /* Accept connection */ + + len = sizeof(sunaddr); + + sock = accept(listen_sock, (struct sockaddr *)(void *)&sunaddr, &len); + + if (sock == -1) { + if (errno != EINTR) { + D_ERR("Failed to accept socket: %s\n", strerror(errno)); + } + return; + } + smb_set_close_on_exec(sock); + + D_INFO("Accepted client socket %d\n", sock); + + /* Create new connection structure */ + + if ((state = talloc_zero(NULL, struct winbindd_cli_state)) == NULL) { + close(sock); + return; + } + + state->sock = sock; + + state->out_queue = tevent_queue_create(state, "winbind client reply"); + if (state->out_queue == NULL) { + close(sock); + TALLOC_FREE(state); + return; + } + + state->privileged = privileged; + + req = wb_req_read_send(state, global_event_context(), state->sock, + WINBINDD_MAX_EXTRA_DATA); + if (req == NULL) { + TALLOC_FREE(state); + close(sock); + return; + } + tevent_req_set_callback(req, winbind_client_request_read, state); + state->io_req = req; + + /* Add to connection list */ + + winbindd_add_client(state); +} + +static void winbind_client_request_read(struct tevent_req *req) +{ + struct winbindd_cli_state *state = tevent_req_callback_data( + req, struct winbindd_cli_state); + ssize_t ret; + int err; + + state->io_req = NULL; + + ret = wb_req_read_recv(req, state, &state->request, &err); + TALLOC_FREE(req); + if (ret == -1) { + if (err == EPIPE) { + DEBUG(6, ("closing socket %d, client exited\n", + state->sock)); + } else { + DEBUG(2, ("Could not read client request from fd %d: " + "%s\n", state->sock, strerror(err))); + } + close(state->sock); + state->sock = -1; + remove_client(state); + return; + } + + req = wait_for_read_send(state, global_event_context(), state->sock, + true); + if (req == NULL) { + DEBUG(0, ("winbind_client_request_read[%d:%s]:" + " wait_for_read_send failed - removing client\n", + (int)state->pid, state->cmd_name)); + remove_client(state); + return; + } + tevent_req_set_callback(req, winbind_client_activity, state); + state->io_req = req; + + req = process_request_send(state, global_event_context(), state); + if (req == NULL) { + DBG_ERR("process_request_send failed\n"); + remove_client(state); + return; + } + tevent_req_set_callback(req, winbind_client_processed, state); +} + +static void winbind_client_activity(struct tevent_req *req) +{ + struct winbindd_cli_state *state = + tevent_req_callback_data(req, struct winbindd_cli_state); + int err; + bool has_data; + + has_data = wait_for_read_recv(req, &err); + + if (has_data) { + DEBUG(0, ("winbind_client_activity[%d:%s]:" + "unexpected data from client - removing client\n", + (int)state->pid, state->cmd_name)); + } else { + if (err == EPIPE) { + DEBUG(6, ("winbind_client_activity[%d:%s]: " + "client has closed connection - removing " + "client\n", + (int)state->pid, state->cmd_name)); + } else { + DEBUG(2, ("winbind_client_activity[%d:%s]: " + "client socket error (%s) - removing " + "client\n", + (int)state->pid, state->cmd_name, + strerror(err))); + } + } + + remove_client(state); +} + +static void winbind_client_processed(struct tevent_req *req) +{ + struct winbindd_cli_state *cli_state = tevent_req_callback_data( + req, struct winbindd_cli_state); + struct tevent_req_profile *profile = NULL; + struct timeval start, stop, diff; + int threshold; + NTSTATUS status; + + status = process_request_recv(req, cli_state, &profile); + TALLOC_FREE(req); + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("process_request failed: %s\n", nt_errstr(status)); + remove_client(cli_state); + return; + } + + tevent_req_profile_get_start(profile, NULL, &start); + tevent_req_profile_get_stop(profile, NULL, &stop); + diff = tevent_timeval_until(&start, &stop); + + threshold = lp_parm_int(-1, "winbind", "request profile threshold", 60); + + if (diff.tv_sec >= threshold) { + int depth; + char *str; + + depth = lp_parm_int( + -1, + "winbind", + "request profile depth", + INT_MAX); + + DBG_ERR("request took %u.%.6u seconds\n", + (unsigned)diff.tv_sec, (unsigned)diff.tv_usec); + + str = tevent_req_profile_string( + talloc_tos(), profile, 0, depth); + if (str != NULL) { + /* No "\n", already contained in "str" */ + DEBUGADD(0, ("%s", str)); + } + TALLOC_FREE(str); + } + + TALLOC_FREE(profile); + + req = wb_req_read_send( + cli_state, + global_event_context(), + cli_state->sock, + WINBINDD_MAX_EXTRA_DATA); + if (req == NULL) { + remove_client(cli_state); + return; + } + tevent_req_set_callback(req, winbind_client_request_read, cli_state); + cli_state->io_req = req; +} + +/* Remove a client connection from client connection list */ + +static void remove_client(struct winbindd_cli_state *state) +{ + /* It's a dead client - hold a funeral */ + + if (state == NULL) { + return; + } + + /* + * We need to remove a pending wb_req_read_* + * or wb_resp_write_* request before closing the + * socket. + * + * This is important as they might have used tevent_add_fd() and we + * use the epoll * backend on linux. So we must remove the tevent_fd + * before closing the fd. + * + * Otherwise we might hit a race with close_conns_after_fork() (via + * winbindd_reinit_after_fork()) where a file descriptor + * is still open in a child, which means it's still active in + * the parents epoll queue, but the related tevent_fd is already + * already gone in the parent. + * + * See bug #11141. + */ + TALLOC_FREE(state->io_req); + + if (state->sock != -1) { + char c = 0; + int nwritten; + + /* tell client, we are closing ... */ + nwritten = write(state->sock, &c, sizeof(c)); + if (nwritten == -1) { + DEBUG(2, ("final write to client failed: %s\n", + strerror(errno))); + } + + /* Close socket */ + + close(state->sock); + state->sock = -1; + } + + TALLOC_FREE(state->mem_ctx); + + /* Remove from list and free */ + + winbindd_remove_client(state); + TALLOC_FREE(state); +} + +/* Is a client idle? */ + +static bool client_is_idle(struct winbindd_cli_state *state) { + return (state->request == NULL && + state->response == NULL && + !state->pwent_state && !state->grent_state); +} + +/* Shutdown client connection which has been idle for the longest time */ + +static bool remove_idle_client(void) +{ + struct winbindd_cli_state *state, *remove_state = NULL; + int nidle = 0; + + for (state = winbindd_client_list(); state; state = state->next) { + if (client_is_idle(state)) { + nidle++; + /* list is sorted by access time */ + remove_state = state; + } + } + + if (remove_state) { + DEBUG(5,("Found %d idle client connections, shutting down sock %d, pid %u\n", + nidle, remove_state->sock, (unsigned int)remove_state->pid)); + remove_client(remove_state); + return True; + } + + return False; +} + +/* + * Terminate all clients whose requests have taken longer than + * "winbind request timeout" seconds to process, or have been + * idle for more than "winbind request timeout" seconds. + */ + +static void remove_timed_out_clients(void) +{ + struct winbindd_cli_state *state, *prev = NULL; + time_t curr_time = time(NULL); + int timeout_val = lp_winbind_request_timeout(); + + for (state = winbindd_client_list_tail(); state; state = prev) { + time_t expiry_time; + + prev = winbindd_client_list_prev(state); + expiry_time = state->last_access + timeout_val; + + if (curr_time <= expiry_time) { + /* list is sorted, previous clients in + list are newer */ + break; + } + + if (client_is_idle(state)) { + DEBUG(5,("Idle client timed out, " + "shutting down sock %d, pid %u\n", + state->sock, + (unsigned int)state->pid)); + } else { + DEBUG(5,("Client request timed out, " + "shutting down sock %d, pid %u\n", + state->sock, + (unsigned int)state->pid)); + } + + remove_client(state); + } +} + +static void winbindd_scrub_clients_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + remove_timed_out_clients(); + if (tevent_add_timer(ev, ev, + timeval_current_ofs(SCRUB_CLIENTS_INTERVAL, 0), + winbindd_scrub_clients_handler, NULL) == NULL) { + DEBUG(0, ("winbindd: failed to reschedule client scrubber\n")); + exit(1); + } +} + +struct winbindd_listen_state { + bool privileged; + int fd; +}; + +static void winbindd_listen_fde_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + struct winbindd_listen_state *s = talloc_get_type_abort(private_data, + struct winbindd_listen_state); + + while (winbindd_num_clients() > lp_winbind_max_clients() - 1) { + DEBUG(5,("winbindd: Exceeding %d client " + "connections, removing idle " + "connection.\n", lp_winbind_max_clients())); + if (!remove_idle_client()) { + DEBUG(0,("winbindd: Exceeding %d " + "client connections, no idle " + "connection found\n", + lp_winbind_max_clients())); + break; + } + } + remove_timed_out_clients(); + new_connection(s->fd, s->privileged); +} + +/* + * Winbindd socket accessor functions + */ + +static bool winbindd_setup_listeners(void) +{ + struct winbindd_listen_state *pub_state = NULL; + struct winbindd_listen_state *priv_state = NULL; + struct tevent_fd *fde; + int rc; + char *socket_path; + + pub_state = talloc(global_event_context(), + struct winbindd_listen_state); + if (!pub_state) { + goto failed; + } + + pub_state->privileged = false; + pub_state->fd = create_pipe_sock( + lp_winbindd_socket_directory(), WINBINDD_SOCKET_NAME, 0755); + if (pub_state->fd == -1) { + goto failed; + } + rc = listen(pub_state->fd, 5); + if (rc < 0) { + goto failed; + } + + fde = tevent_add_fd(global_event_context(), pub_state, pub_state->fd, + TEVENT_FD_READ, winbindd_listen_fde_handler, + pub_state); + if (fde == NULL) { + close(pub_state->fd); + goto failed; + } + tevent_fd_set_auto_close(fde); + + priv_state = talloc(global_event_context(), + struct winbindd_listen_state); + if (!priv_state) { + goto failed; + } + + socket_path = get_winbind_priv_pipe_dir(); + if (socket_path == NULL) { + goto failed; + } + + priv_state->privileged = true; + priv_state->fd = create_pipe_sock( + socket_path, WINBINDD_SOCKET_NAME, 0750); + TALLOC_FREE(socket_path); + if (priv_state->fd == -1) { + goto failed; + } + rc = listen(priv_state->fd, 5); + if (rc < 0) { + goto failed; + } + + fde = tevent_add_fd(global_event_context(), priv_state, + priv_state->fd, TEVENT_FD_READ, + winbindd_listen_fde_handler, priv_state); + if (fde == NULL) { + close(priv_state->fd); + goto failed; + } + tevent_fd_set_auto_close(fde); + + winbindd_scrub_clients_handler(global_event_context(), NULL, + timeval_current(), NULL); + return true; +failed: + TALLOC_FREE(pub_state); + TALLOC_FREE(priv_state); + return false; +} + +static void winbindd_register_handlers(struct messaging_context *msg_ctx, + bool foreground) +{ + bool scan_trusts = true; + NTSTATUS status; + struct tevent_timer *te = NULL; + + /* Setup signal handlers */ + + if (!winbindd_setup_sig_term_handler(true)) + exit(1); + if (!winbindd_setup_stdin_handler(true, foreground)) + exit(1); + if (!winbindd_setup_sig_hup_handler(NULL)) + exit(1); + if (!winbindd_setup_sig_chld_handler()) + exit(1); + if (!winbindd_setup_sig_usr2_handler()) + exit(1); + + CatchSignal(SIGPIPE, SIG_IGN); /* Ignore sigpipe */ + + /* + * Ensure all cache and idmap caches are consistent + * and initialized before we startup. + */ + if (!winbindd_cache_validate_and_initialize()) { + exit(1); + } + + /* React on 'smbcontrol winbindd reload-config' in the same way + as to SIGHUP signal */ + messaging_register(msg_ctx, NULL, + MSG_SMB_CONF_UPDATED, + winbindd_msg_reload_services_parent); + messaging_register(msg_ctx, NULL, + MSG_SHUTDOWN, msg_shutdown); + + /* Handle online/offline messages. */ + messaging_register(msg_ctx, NULL, + MSG_WINBIND_OFFLINE, winbind_msg_offline); + messaging_register(msg_ctx, NULL, + MSG_WINBIND_ONLINE, winbind_msg_online); + messaging_register(msg_ctx, NULL, + MSG_WINBIND_ONLINESTATUS, winbind_msg_onlinestatus); + + /* Handle domain online/offline messages for domains */ + messaging_register(global_messaging_context(), NULL, + MSG_WINBIND_DOMAIN_OFFLINE, winbind_msg_domain_offline); + messaging_register(global_messaging_context(), NULL, + MSG_WINBIND_DOMAIN_ONLINE, winbind_msg_domain_online); + + messaging_register(msg_ctx, NULL, + MSG_WINBIND_VALIDATE_CACHE, + winbind_msg_validate_cache); + + messaging_register(msg_ctx, NULL, + MSG_WINBIND_DUMP_DOMAIN_LIST, + winbind_msg_dump_domain_list); + + messaging_register(msg_ctx, NULL, + MSG_WINBIND_IP_DROPPED, + winbind_msg_ip_dropped_parent); + + /* Register handler for MSG_DEBUG. */ + messaging_register(msg_ctx, NULL, + MSG_DEBUG, + winbind_msg_debug); + + messaging_register(msg_ctx, NULL, + MSG_WINBIND_DISCONNECT_DC, + winbind_disconnect_dc_parent); + + netsamlogon_cache_init(); /* Non-critical */ + + /* clear the cached list of trusted domains */ + + wcache_tdc_clear(); + + if (!init_domain_list()) { + DEBUG(0,("unable to initialize domain list\n")); + exit(1); + } + + status = init_idmap_child(global_event_context()); + if (NT_STATUS_IS_ERR(status)) { + DBG_ERR("Unable to start idmap child: %s\n", nt_errstr(status)); + exit(1); + } + + status = init_locator_child(global_event_context()); + if (NT_STATUS_IS_ERR(status)) { + DBG_ERR("Unable to start locator child: %s\n", nt_errstr(status)); + exit(1); + } + + smb_nscd_flush_user_cache(); + smb_nscd_flush_group_cache(); + + if (!lp_winbind_scan_trusted_domains()) { + scan_trusts = false; + } + + if (!lp_allow_trusted_domains()) { + scan_trusts = false; + } + + if (IS_DC) { + scan_trusts = false; + } + + if (scan_trusts) { + if (tevent_add_timer(global_event_context(), NULL, timeval_zero(), + rescan_trusted_domains, NULL) == NULL) { + DEBUG(0, ("Could not trigger rescan_trusted_domains()\n")); + exit(1); + } + } + + te = tevent_add_timer(global_event_context(), + NULL, + timeval_zero(), + winbindd_ping_offline_domains, + NULL); + if (te == NULL) { + DBG_ERR("Failed to schedule winbindd_ping_offline_domains()\n"); + exit(1); + } + + status = wb_irpc_register(); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Could not register IRPC handlers\n")); + exit(1); + } +} + +struct winbindd_addrchanged_state { + struct addrchange_context *ctx; + struct tevent_context *ev; + struct messaging_context *msg_ctx; +}; + +static void winbindd_addr_changed(struct tevent_req *req); + +static void winbindd_init_addrchange(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct messaging_context *msg_ctx) +{ + struct winbindd_addrchanged_state *state; + struct tevent_req *req; + NTSTATUS status; + + state = talloc(mem_ctx, struct winbindd_addrchanged_state); + if (state == NULL) { + DEBUG(10, ("talloc failed\n")); + return; + } + state->ev = ev; + state->msg_ctx = msg_ctx; + + status = addrchange_context_create(state, &state->ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("addrchange_context_create failed: %s\n", + nt_errstr(status))); + TALLOC_FREE(state); + return; + } + req = addrchange_send(state, ev, state->ctx); + if (req == NULL) { + DEBUG(0, ("addrchange_send failed\n")); + TALLOC_FREE(state); + return; + } + tevent_req_set_callback(req, winbindd_addr_changed, state); +} + +static void winbindd_addr_changed(struct tevent_req *req) +{ + struct winbindd_addrchanged_state *state = tevent_req_callback_data( + req, struct winbindd_addrchanged_state); + enum addrchange_type type; + struct sockaddr_storage addr; + NTSTATUS status; + + status = addrchange_recv(req, &type, &addr); + TALLOC_FREE(req); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("addrchange_recv failed: %s, stop listening\n", + nt_errstr(status))); + TALLOC_FREE(state); + return; + } + if (type == ADDRCHANGE_DEL) { + char addrstr[INET6_ADDRSTRLEN]; + DATA_BLOB blob; + + print_sockaddr(addrstr, sizeof(addrstr), &addr); + + DEBUG(3, ("winbindd: kernel (AF_NETLINK) dropped ip %s\n", + addrstr)); + + blob = data_blob_const(addrstr, strlen(addrstr)+1); + + status = messaging_send(state->msg_ctx, + messaging_server_id(state->msg_ctx), + MSG_WINBIND_IP_DROPPED, &blob); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("messaging_send failed: %s - ignoring\n", + nt_errstr(status))); + } + } + req = addrchange_send(state, state->ev, state->ctx); + if (req == NULL) { + DEBUG(0, ("addrchange_send failed\n")); + TALLOC_FREE(state); + return; + } + tevent_req_set_callback(req, winbindd_addr_changed, state); +} + +/* Main function */ + +int main(int argc, const char **argv) +{ + static bool log_stdout = False; + struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL; + struct poptOption long_options[] = { + POPT_AUTOHELP + { + .longName = "no-caching", + .shortName = 'n', + .argInfo = POPT_ARG_NONE, + .arg = NULL, + .val = 'n', + .descrip = "Disable caching", + }, + POPT_COMMON_SAMBA + POPT_COMMON_DAEMON + POPT_COMMON_VERSION + POPT_TABLEEND + }; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + poptContext pc; + int opt; + TALLOC_CTX *frame; + NTSTATUS status; + bool ok; + const struct dcesrv_endpoint_server *ep_server = NULL; + struct dcesrv_context *dce_ctx = NULL; + size_t winbindd_socket_dir_len = 0; + char *winbindd_priv_socket_dir = NULL; + size_t winbindd_priv_socket_dir_len = 0; + + setproctitle_init(argc, discard_const(argv), environ); + + /* + * Do this before any other talloc operation + */ + talloc_enable_null_tracking(); + frame = talloc_stackframe(); + + /* + * We want total control over the permissions on created files, + * so set our umask to 0. + */ + umask(0); + + smb_init_locale(); + + /* glibc (?) likes to print "User defined signal 1" and exit if a + SIGUSR[12] is received before a handler is installed */ + + CatchSignal(SIGUSR1, SIG_IGN); + CatchSignal(SIGUSR2, SIG_IGN); + + ok = samba_cmdline_init(frame, + SAMBA_CMDLINE_CONFIG_SERVER, + true /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to setup cmdline parser\n"); + TALLOC_FREE(frame); + exit(1); + } + + cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg(); + + pc = samba_popt_get_context(getprogname(), argc, argv, long_options, 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt parser!\n"); + TALLOC_FREE(frame); + exit(1); + } + + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + case 'n': + winbindd_set_use_cache(false); + break; + default: + d_fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + exit(1); + } + } + + /* Set environment variable so we don't recursively call ourselves. + This may also be useful interactively. */ + if ( !winbind_off() ) { + DEBUG(0,("Failed to disable recursive winbindd calls. Exiting.\n")); + exit(1); + } + + /* Initialise for running in non-root mode */ + sec_init(); + + set_remote_machine_name("winbindd", False); + + dump_core_setup("winbindd", lp_logfile(talloc_tos(), lp_sub)); + if (cmdline_daemon_cfg->daemon && cmdline_daemon_cfg->interactive) { + d_fprintf(stderr,"\nERROR: " + "Option -i|--interactive is not allowed together with -D|--daemon\n\n"); + poptPrintUsage(pc, stderr, 0); + exit(1); + } + + log_stdout = (debug_get_log_type() == DEBUG_STDOUT); + if (cmdline_daemon_cfg->interactive) { + /* + * libcmdline POPT_DAEMON callback sets "fork" to false if "-i" + * for interactive is passed on the commandline. Set it back to + * true. TODO: check if this is correct, smbd and nmbd don't do + * this. + */ + cmdline_daemon_cfg->fork = true; + log_stdout = true; + } + + if (log_stdout && cmdline_daemon_cfg->fork) { + d_fprintf(stderr, "\nERROR: " + "Can't log to stdout (-S) unless daemon is in " + "foreground (-F) or interactive (-i)\n\n"); + poptPrintUsage(pc, stderr, 0); + exit(1); + } + + poptFreeContext(pc); + + reopen_logs(); + + DBG_STARTUP_NOTICE("winbindd version %s started.\n%s\n", + samba_version_string(), + samba_copyright_string()); + + /* After parsing the configuration file we setup the core path one more time + * as the log file might have been set in the configuration and cores's + * path is by default basename(lp_logfile()). + */ + dump_core_setup("winbindd", lp_logfile(talloc_tos(), lp_sub)); + + if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + if (!lp_parm_bool(-1, "server role check", "inhibit", false)) { + DBG_ERR("server role = 'active directory domain controller' not compatible with running the winbindd binary. \n"); + DEBUGADD(0, ("You should start 'samba' instead, and it will control starting the internal AD DC winbindd implementation, which is not the same as this one\n")); + exit(1); + } + /* Main 'samba' daemon will notify */ + daemon_sd_notifications(false); + } + + if (lp_security() == SEC_ADS) { + const char *realm = lp_realm(); + const char *workgroup = lp_workgroup(); + + if (workgroup == NULL || strlen(workgroup) == 0) { + DBG_ERR("For 'secuirity = ADS' mode, the 'workgroup' " + "parameter is required to be set!\n"); + exit(1); + } + + if (realm == NULL || strlen(realm) == 0) { + DBG_ERR("For 'secuirity = ADS' mode, the 'realm' " + "parameter is required to be set!\n"); + exit(1); + } + } + + winbindd_socket_dir_len = strlen(lp_winbindd_socket_directory()); + if (winbindd_socket_dir_len > 0) { + size_t winbindd_socket_len = + winbindd_socket_dir_len + 1 + + strlen(WINBINDD_SOCKET_NAME); + struct sockaddr_un un = { + .sun_family = AF_UNIX, + }; + size_t sun_path_len = sizeof(un.sun_path); + + if (winbindd_socket_len >= sun_path_len) { + DBG_ERR("The winbind socket path [%s/%s] is too long " + "(%zu >= %zu)\n", + lp_winbindd_socket_directory(), + WINBINDD_SOCKET_NAME, + winbindd_socket_len, + sun_path_len); + exit(1); + } + } else { + DBG_ERR("'winbindd_socket_directory' parameter is empty\n"); + exit(1); + } + + winbindd_priv_socket_dir = get_winbind_priv_pipe_dir(); + winbindd_priv_socket_dir_len = strlen(winbindd_priv_socket_dir); + if (winbindd_priv_socket_dir_len > 0) { + size_t winbindd_priv_socket_len = + winbindd_priv_socket_dir_len + 1 + + strlen(WINBINDD_SOCKET_NAME); + struct sockaddr_un un = { + .sun_family = AF_UNIX, + }; + size_t sun_path_len = sizeof(un.sun_path); + + if (winbindd_priv_socket_len >= sun_path_len) { + DBG_ERR("The winbind privileged socket path [%s/%s] is too long " + "(%zu >= %zu)\n", + winbindd_priv_socket_dir, + WINBINDD_SOCKET_NAME, + winbindd_priv_socket_len, + sun_path_len); + exit(1); + } + } else { + DBG_ERR("'winbindd_priv_socket_directory' parameter is empty\n"); + exit(1); + } + TALLOC_FREE(winbindd_priv_socket_dir); + + if (!cluster_probe_ok()) { + exit(1); + } + + /* Initialise messaging system */ + + if (global_messaging_context() == NULL) { + exit(1); + } + + if (!winbindd_reload_services_file(NULL)) { + DEBUG(0, ("error opening config file\n")); + exit(1); + } + + { + size_t i; + const char *idmap_backend; + const char *invalid_backends[] = { + "ad", "rfc2307", "rid", + }; + + idmap_backend = lp_idmap_default_backend(); + for (i = 0; i < ARRAY_SIZE(invalid_backends); i++) { + ok = strequal(idmap_backend, invalid_backends[i]); + if (ok) { + DBG_ERR("FATAL: Invalid idmap backend %s " + "configured as the default backend!\n", + idmap_backend); + exit(1); + } + } + } + + ok = directory_create_or_exist(lp_lock_directory(), 0755); + if (!ok) { + DEBUG(0, ("Failed to create directory %s for lock files - %s\n", + lp_lock_directory(), strerror(errno))); + exit(1); + } + + ok = directory_create_or_exist(lp_pid_directory(), 0755); + if (!ok) { + DEBUG(0, ("Failed to create directory %s for pid files - %s\n", + lp_pid_directory(), strerror(errno))); + exit(1); + } + + load_interfaces(); + + if (!secrets_init()) { + + DEBUG(0,("Could not initialize domain trust account secrets. Giving up\n")); + return False; + } + + status = rpccli_pre_open_netlogon_creds(); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("rpccli_pre_open_netlogon_creds() - %s\n", + nt_errstr(status))); + exit(1); + } + + /* Unblock all signals we are interested in as they may have been + blocked by the parent process. */ + + BlockSignals(False, SIGINT); + BlockSignals(False, SIGQUIT); + BlockSignals(False, SIGTERM); + BlockSignals(False, SIGUSR1); + BlockSignals(False, SIGUSR2); + BlockSignals(False, SIGHUP); + BlockSignals(False, SIGCHLD); + + if (!interactive) { + become_daemon(cmdline_daemon_cfg->fork, + cmdline_daemon_cfg->no_process_group, + log_stdout); + } else { + daemon_status("winbindd", "Starting process ..."); + } + + pidfile_create(lp_pid_directory(), "winbindd"); + +#ifdef HAVE_SETPGID + /* + * If we're interactive we want to set our own process group for + * signal management. + */ + if (cmdline_daemon_cfg->interactive && + !cmdline_daemon_cfg->no_process_group) + { + setpgid( (pid_t)0, (pid_t)0); + } +#endif + + TimeInit(); + + /* Don't use winbindd_reinit_after_fork here as + * we're just starting up and haven't created any + * winbindd-specific resources we must free yet. JRA. + */ + + status = reinit_after_fork(global_messaging_context(), + global_event_context(), + false); + if (!NT_STATUS_IS_OK(status)) { + exit_daemon("Winbindd reinit_after_fork() failed", map_errno_from_nt_status(status)); + } + + if (lp_winbind_debug_traceid()) { + winbind_debug_traceid_setup(global_event_context()); + winbind_debug_call_depth_setup(debug_call_depth_addr()); + tevent_thread_call_depth_set_callback( + debuglevel_get() > 1 ? winbind_call_flow : NULL, + NULL); + } + ok = initialize_password_db(true, global_event_context()); + if (!ok) { + exit_daemon("Failed to initialize passdb backend! " + "Check the 'passdb backend' variable in your " + "smb.conf file.", EINVAL); + } + + /* + * Do not initialize the parent-child-pipe before becoming + * a daemon: this is used to detect a died parent in the child + * process. + */ + status = init_before_fork(); + if (!NT_STATUS_IS_OK(status)) { + exit_daemon(nt_errstr(status), map_errno_from_nt_status(status)); + } + + winbindd_register_handlers(global_messaging_context(), + !cmdline_daemon_cfg->fork); + + if (!messaging_parent_dgm_cleanup_init(global_messaging_context())) { + exit(1); + } + + status = init_system_session_info(NULL); + if (!NT_STATUS_IS_OK(status)) { + exit_daemon("Winbindd failed to setup system user info", map_errno_from_nt_status(status)); + } + + DBG_INFO("Registering DCE/RPC endpoint servers\n"); + + ep_server = winbind_get_ep_server(); + if (ep_server == NULL) { + DBG_ERR("Failed to get 'winbind' endpoint server\n"); + exit(1); + } + status = dcerpc_register_ep_server(ep_server); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to register 'winbind' endpoint " + "server: %s\n", nt_errstr(status)); + exit(1); + } + + dce_ctx = global_dcesrv_context(); + + DBG_INFO("Initializing DCE/RPC registered endpoint servers\n"); + + /* Init all registered ep servers */ + status = dcesrv_init_registered_ep_servers(dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to init DCE/RPC endpoint servers: %s\n", + nt_errstr(status)); + exit(1); + } + + winbindd_init_addrchange(NULL, global_event_context(), + global_messaging_context()); + + /* setup listen sockets */ + + if (!winbindd_setup_listeners()) { + exit_daemon("Winbindd failed to setup listeners", EPIPE); + } + + irpc_add_name(winbind_imessaging_context(), "winbind_server"); + + TALLOC_FREE(frame); + + if (!interactive) { + daemon_ready("winbindd"); + } + + gpupdate_init(); + + /* Loop waiting for requests */ + while (1) { + frame = talloc_stackframe(); + + if (tevent_loop_once(global_event_context()) == -1) { + DEBUG(1, ("tevent_loop_once() failed: %s\n", + strerror(errno))); + return 1; + } + + TALLOC_FREE(frame); + } + + return 0; +} diff --git a/source3/winbindd/winbindd.h b/source3/winbindd/winbindd.h new file mode 100644 index 0000000..53430a6 --- /dev/null +++ b/source3/winbindd/winbindd.h @@ -0,0 +1,370 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) Tim Potter 2000 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _WINBINDD_H +#define _WINBINDD_H + +#include "nsswitch/winbind_struct_protocol.h" +#include "nsswitch/libwbclient/wbclient.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "librpc/gen_ndr/winbind.h" + +#include "../lib/util/tevent_ntstatus.h" + +#ifdef HAVE_LIBNSCD +#include <libnscd.h> +#endif + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +#define WB_REPLACE_CHAR '_' + +struct winbind_internal_pipes; +struct ads_struct; + +struct winbindd_cli_state { + struct winbindd_cli_state *prev, *next; /* Linked list pointers */ + int sock; /* Open socket from client */ + pid_t pid; /* pid of client */ + char client_name[32]; /* The process name of the client */ + time_t last_access; /* Time of last access (read or write) */ + bool privileged; /* Is the client 'privileged' */ + + TALLOC_CTX *mem_ctx; /* memory per request */ + const char *cmd_name; + NTSTATUS (*recv_fn)(struct tevent_req *req, + struct winbindd_response *presp); + struct winbindd_request *request; /* Request from client */ + struct tevent_queue *out_queue; + struct winbindd_response *response; /* Response to client */ + struct tevent_req *io_req; /* wb_req_read_* or wb_resp_write_* */ + + struct getpwent_state *pwent_state; /* State for getpwent() */ + struct getgrent_state *grent_state; /* State for getgrent() */ +}; + +struct getpwent_state { + struct winbindd_domain *domain; + uint32_t next_user; + struct wbint_RidArray rids; +}; + +struct getgrent_state { + struct winbindd_domain *domain; + uint32_t next_group; + uint32_t num_groups; + struct wbint_Principal *groups; +}; + +/* Our connection to the DC */ + +struct winbindd_cm_conn { + struct cli_state *cli; + + enum dcerpc_AuthLevel auth_level; + + struct rpc_pipe_client *samr_pipe; + struct policy_handle sam_connect_handle, sam_domain_handle; + + struct rpc_pipe_client *lsa_pipe; + struct rpc_pipe_client *lsa_pipe_tcp; + struct policy_handle lsa_policy; + + struct rpc_pipe_client *netlogon_pipe; + struct netlogon_creds_cli_context *netlogon_creds_ctx; + bool netlogon_force_reauth; +}; + +/* Async child */ + +struct winbindd_domain; + +struct winbindd_child { + pid_t pid; + struct winbindd_domain *domain; + char *logfilename; + + int sock; + struct tevent_fd *monitor_fde; /* Watch for dead children/sockets */ + struct tevent_queue *queue; + struct dcerpc_binding_handle *binding_handle; + + struct tevent_timer *lockout_policy_event; + struct tevent_timer *machine_password_change_event; +}; + +/* Structures to hold per domain information */ + +struct winbindd_domain { + char *name; /* Domain name (NetBIOS) */ + char *alt_name; /* alt Domain name, if any (FQDN for ADS) */ + char *forest_name; /* Name of the AD forest we're in */ + struct dom_sid sid; /* SID for this domain */ + enum netr_SchannelType secure_channel_type; + uint32_t domain_flags; /* Domain flags from netlogon.h */ + uint32_t domain_type; /* Domain type from netlogon.h */ + uint32_t domain_trust_attribs; /* Trust attribs from netlogon.h */ + struct winbindd_domain *routing_domain; + bool initialized; /* Did we already ask for the domain mode? */ + bool native_mode; /* is this a win2k domain in native mode ? */ + bool active_directory; /* is this a win2k active directory ? */ + bool primary; /* is this our primary domain ? */ + bool internal; /* BUILTIN and member SAM */ + bool rodc; /* Are we an RODC for this AD domain? (do some operations locally) */ + bool online; /* is this domain available ? */ + time_t startup_time; /* When we set "startup" true. monotonic clock */ + bool startup; /* are we in the first 30 seconds after startup_time ? */ + + bool can_do_ncacn_ip_tcp; + + /* + * Lookup methods for this domain (LDAP or RPC). The backend + * methods are used by the cache layer. + */ + struct winbindd_methods *backend; + + struct { + struct winbind_internal_pipes *samr_pipes; + struct ads_struct *ads_conn; + } backend_data; + + /* A working DC */ + bool force_dc; + char *dcname; + const char *ping_dcname; + struct sockaddr_storage dcaddr; + + /* Sequence number stuff */ + + time_t last_seq_check; + uint32_t sequence_number; + NTSTATUS last_status; + + /* The smb connection */ + + struct winbindd_cm_conn conn; + + /* The child pid we're talking to */ + + struct winbindd_child *children; + + struct tevent_queue *queue; + struct dcerpc_binding_handle *binding_handle; + + struct tevent_req *check_online_event; + + /* Linked list info */ + + struct winbindd_domain *prev, *next; +}; + +struct wb_parent_idmap_config_dom { + unsigned low_id; + unsigned high_id; + const char *name; + struct dom_sid sid; +}; + +struct wb_parent_idmap_config { + struct tevent_queue *queue; + uint32_t num_doms; + bool initialized; + struct wb_parent_idmap_config_dom *doms; +}; + +struct wb_acct_info { + const char *acct_name; /* account name */ + const char *acct_desc; /* account name */ + uint32_t rid; /* domain-relative RID */ +}; + +/* per-domain methods. This is how LDAP vs RPC is selected + */ +struct winbindd_methods { + /* does this backend provide a consistent view of the data? (ie. is the primary group + always correct) */ + bool consistent; + + /* get a list of users, returning a wbint_userinfo for each one */ + NTSTATUS (*query_user_list)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **rids); + + /* get a list of domain groups */ + NTSTATUS (*enum_dom_groups)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info); + + /* get a list of domain local groups */ + NTSTATUS (*enum_local_groups)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info); + + /* convert one user or group name to a sid */ + NTSTATUS (*name_to_sid)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + uint32_t flags, + const char **pdom_name, + struct dom_sid *sid, + enum lsa_SidType *type); + + /* convert a sid to a user or group name */ + NTSTATUS (*sid_to_name)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + char **domain_name, + char **name, + enum lsa_SidType *type); + + NTSTATUS (*rids_to_names)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *domain_sid, + uint32_t *rids, + size_t num_rids, + char **domain_name, + char ***names, + enum lsa_SidType **types); + + /* lookup all groups that a user is a member of. The backend + can also choose to lookup by username or rid for this + function */ + NTSTATUS (*lookup_usergroups)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *num_groups, struct dom_sid **user_gids); + + /* Lookup all aliases that the sids delivered are member of. This is + * to implement 'domain local groups' correctly */ + NTSTATUS (*lookup_useraliases)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *num_aliases, + uint32_t **alias_rids); + + /* find all members of the group with the specified group_rid */ + NTSTATUS (*lookup_groupmem)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem, char ***names, + uint32_t **name_types); + + /* find all members of the alias with the specified alias_sid */ + NTSTATUS (*lookup_aliasmem)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *alias_sid, + enum lsa_SidType type, + uint32_t *num_sids, + struct dom_sid **sid_mem); + + /* return the lockout policy */ + NTSTATUS (*lockout_policy)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *lockout_policy); + + /* return the lockout policy */ + NTSTATUS (*password_policy)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *password_policy); + + /* enumerate trusted domains */ + NTSTATUS (*trusted_domains)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *trusts); +}; + +/* Filled out by IDMAP backends */ +struct winbindd_idmap_methods { + /* Called when backend is first loaded */ + bool (*init)(void); + + bool (*get_sid_from_uid)(uid_t uid, struct dom_sid *sid); + bool (*get_sid_from_gid)(gid_t gid, struct dom_sid *sid); + + bool (*get_uid_from_sid)(struct dom_sid *sid, uid_t *uid); + bool (*get_gid_from_sid)(struct dom_sid *sid, gid_t *gid); + + /* Called when backend is unloaded */ + bool (*close)(void); + /* Called to dump backend status */ + void (*status)(void); +}; + +/* Data structures for dealing with the trusted domain cache */ + +struct winbindd_tdc_domain { + const char *domain_name; + const char *dns_name; + struct dom_sid sid; + uint32_t trust_flags; + uint32_t trust_attribs; + uint32_t trust_type; +}; + +struct WINBINDD_MEMORY_CREDS { + struct WINBINDD_MEMORY_CREDS *next, *prev; + const char *username; /* lookup key. */ + uid_t uid; + int ref_count; + size_t len; + uint8_t *nt_hash; /* Base pointer for the following 2 */ + uint8_t *lm_hash; + char *pass; +}; + +struct WINBINDD_CCACHE_ENTRY { + struct WINBINDD_CCACHE_ENTRY *next, *prev; + const char *principal_name; + const char *ccname; + const char *service; + const char *username; + const char *realm; + const char *canon_principal; + const char *canon_realm; + struct WINBINDD_MEMORY_CREDS *cred_ptr; + int ref_count; + uid_t uid; + time_t create_time; + time_t renew_until; + time_t refresh_time; + struct tevent_timer *event; +}; + +#include "winbindd/winbindd_proto.h" + +#define WINBINDD_ESTABLISH_LOOP 30 +#define WINBINDD_RESCAN_FREQ lp_winbind_cache_time() +#define WINBINDD_PAM_AUTH_KRB5_RENEW_TIME 2592000 /* one month */ +#define DOM_SEQUENCE_NONE ((uint32_t)-1) + +#endif /* _WINBINDD_H */ diff --git a/source3/winbindd/winbindd_ads.c b/source3/winbindd/winbindd_ads.c new file mode 100644 index 0000000..7e572e5 --- /dev/null +++ b/source3/winbindd/winbindd_ads.c @@ -0,0 +1,1604 @@ +/* + Unix SMB/CIFS implementation. + + Winbind ADS backend functions + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003 + Copyright (C) Gerald (Jerry) Carter 2004 + + 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.h" +#include "winbindd_ads.h" +#include "libsmb/namequery.h" +#include "rpc_client/rpc_client.h" +#include "../librpc/gen_ndr/ndr_netlogon_c.h" +#include "../libds/common/flags.h" +#include "ads.h" +#include "../libcli/ldap/ldap_ndr.h" +#include "../libcli/security/security.h" +#include "../libds/common/flag_mapping.h" +#include "libsmb/samlogon_cache.h" +#include "passdb.h" +#include "auth/credentials/credentials.h" + +#ifdef HAVE_ADS + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +extern struct winbindd_methods reconnect_methods; +extern struct winbindd_methods msrpc_methods; + +#define WINBIND_CCACHE_NAME "MEMORY:winbind_ccache" + +/** + * Check if cached connection can be reused. If the connection cannot + * be reused the ADS_STRUCT is freed and the pointer is set to NULL. + */ +static void ads_cached_connection_reuse(ADS_STRUCT **adsp) +{ + + ADS_STRUCT *ads = *adsp; + + if (ads != NULL) { + time_t expire; + time_t now = time(NULL); + + expire = MIN(ads->auth.tgt_expire, ads->auth.tgs_expire); + + DEBUG(7, ("Current tickets expire in %d seconds (at %d, time " + "is now %d)\n", (uint32_t)expire - (uint32_t)now, + (uint32_t) expire, (uint32_t) now)); + + if ( ads->config.realm && (expire > now)) { + return; + } else { + /* we own this ADS_STRUCT so make sure it goes away */ + DEBUG(7,("Deleting expired krb5 credential cache\n")); + TALLOC_FREE(ads); + ads_kdestroy(WINBIND_CCACHE_NAME); + *adsp = NULL; + } + } +} + +/** + * @brief Establish a connection to a DC + * + * @param[out] adsp ADS_STRUCT that will be created + * @param[in] target_realm Realm of domain to connect to + * @param[in] target_dom_name 'workgroup' name of domain to connect to + * @param[in] ldap_server DNS name of server to connect to + * @param[in] password Our machine account secret + * @param[in] auth_realm Realm of local domain for creating krb token + * @param[in] renewable Renewable ticket time + * + * @return ADS_STATUS + */ +static ADS_STATUS ads_cached_connection_connect(const char *target_realm, + const char *target_dom_name, + const char *ldap_server, + char *password, + char *auth_realm, + time_t renewable, + TALLOC_CTX *mem_ctx, + ADS_STRUCT **adsp) +{ + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + ADS_STRUCT *ads; + ADS_STATUS status; + struct sockaddr_storage dc_ss; + fstring dc_name; + enum credentials_use_kerberos krb5_state; + + if (auth_realm == NULL) { + TALLOC_FREE(tmp_ctx); + return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + } + + /* we don't want this to affect the users ccache */ + setenv("KRB5CCNAME", WINBIND_CCACHE_NAME, 1); + + ads = ads_init(tmp_ctx, + target_realm, + target_dom_name, + ldap_server, + ADS_SASL_SEAL); + if (!ads) { + DEBUG(1,("ads_init for domain %s failed\n", target_dom_name)); + status = ADS_ERROR(LDAP_NO_MEMORY); + goto out; + } + + ADS_TALLOC_CONST_FREE(ads->auth.password); + ADS_TALLOC_CONST_FREE(ads->auth.realm); + + ads->auth.renewable = renewable; + ads->auth.password = talloc_strdup(ads, password); + if (ads->auth.password == NULL) { + status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + goto out; + } + + /* In FIPS mode, client use kerberos is forced to required. */ + krb5_state = lp_client_use_kerberos(); + switch (krb5_state) { + case CRED_USE_KERBEROS_REQUIRED: + ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS; + ads->auth.flags &= ~ADS_AUTH_ALLOW_NTLMSSP; + break; + case CRED_USE_KERBEROS_DESIRED: + ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS; + ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP; + break; + case CRED_USE_KERBEROS_DISABLED: + ads->auth.flags |= ADS_AUTH_DISABLE_KERBEROS; + ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP; + break; + } + + ads->auth.realm = talloc_asprintf_strupper_m(ads, "%s", auth_realm); + if (ads->auth.realm == NULL) { + status = ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR); + goto out; + } + + /* Setup the server affinity cache. We don't reaally care + about the name. Just setup affinity and the KRB5_CONFIG + file. */ + get_dc_name(ads->server.workgroup, ads->server.realm, dc_name, &dc_ss); + + status = ads_connect(ads); + if (!ADS_ERR_OK(status)) { + DEBUG(1,("ads_connect for domain %s failed: %s\n", + target_dom_name, ads_errstr(status))); + goto out; + } + + *adsp = talloc_move(mem_ctx, &ads); +out: + TALLOC_FREE(tmp_ctx); + return status; +} + +ADS_STATUS ads_idmap_cached_connection(const char *dom_name, + TALLOC_CTX *mem_ctx, + ADS_STRUCT **adsp) +{ + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + char *ldap_server = NULL; + char *realm = NULL; + char *password = NULL; + struct winbindd_domain *wb_dom = NULL; + ADS_STATUS status; + + if (IS_AD_DC) { + /* + * Make sure we never try to use LDAP against + * a trusted domain as AD DC. + */ + TALLOC_FREE(tmp_ctx); + return ADS_ERROR_NT(NT_STATUS_REQUEST_NOT_ACCEPTED); + } + + ads_cached_connection_reuse(adsp); + if (*adsp != NULL) { + TALLOC_FREE(tmp_ctx); + return ADS_SUCCESS; + } + + /* + * At this point we only have the NetBIOS domain name. + * Check if we can get server name and realm from SAF cache + * and the domain list. + */ + ldap_server = saf_fetch(tmp_ctx, dom_name); + + DBG_DEBUG("ldap_server from saf cache: '%s'\n", + ldap_server ? ldap_server : ""); + + wb_dom = find_domain_from_name(dom_name); + if (wb_dom == NULL) { + DBG_DEBUG("could not find domain '%s'\n", dom_name); + status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL); + goto out; + } + + DBG_DEBUG("find_domain_from_name found realm '%s' for " + " domain '%s'\n", wb_dom->alt_name, dom_name); + + if (!get_trust_pw_clear(dom_name, &password, NULL, NULL)) { + status = ADS_ERROR_NT(NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + goto out; + } + + if (IS_DC) { + SMB_ASSERT(wb_dom->alt_name != NULL); + realm = talloc_strdup(tmp_ctx, wb_dom->alt_name); + } else { + struct winbindd_domain *our_domain = wb_dom; + + /* always give preference to the alt_name in our + primary domain if possible */ + + if (!wb_dom->primary) { + our_domain = find_our_domain(); + } + + if (our_domain->alt_name != NULL) { + realm = talloc_strdup(tmp_ctx, our_domain->alt_name); + } else { + realm = talloc_strdup(tmp_ctx, lp_realm()); + } + } + + if (realm == NULL) { + status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + goto out; + } + + status = ads_cached_connection_connect( + wb_dom->alt_name, /* realm to connect to. */ + dom_name, /* 'workgroup' name for ads_init */ + ldap_server, /* DNS name to connect to. */ + password, /* password for auth realm. */ + realm, /* realm used for krb5 ticket. */ + 0, /* renewable ticket time. */ + mem_ctx, /* memory context for ads struct */ + adsp); /* Returns ads struct. */ + +out: + TALLOC_FREE(tmp_ctx); + SAFE_FREE(password); + + return status; +} + +/* + return our ads connections structure for a domain. We keep the connection + open to make things faster +*/ +static ADS_STATUS ads_cached_connection(struct winbindd_domain *domain, + ADS_STRUCT **adsp) +{ + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + ADS_STATUS status; + char *password = NULL; + char *realm = NULL; + + if (IS_AD_DC) { + /* + * Make sure we never try to use LDAP against + * a trusted domain as AD DC. + */ + TALLOC_FREE(tmp_ctx); + return ADS_ERROR_NT(NT_STATUS_REQUEST_NOT_ACCEPTED); + } + + DBG_DEBUG("ads_cached_connection\n"); + + ads_cached_connection_reuse(&domain->backend_data.ads_conn); + if (domain->backend_data.ads_conn != NULL) { + *adsp = domain->backend_data.ads_conn; + TALLOC_FREE(tmp_ctx); + return ADS_SUCCESS; + } + + /* the machine acct password might have change - fetch it every time */ + + if (!get_trust_pw_clear(domain->name, &password, NULL, NULL)) { + status = ADS_ERROR_NT(NT_STATUS_CANT_ACCESS_DOMAIN_INFO); + goto out; + } + + if ( IS_DC ) { + SMB_ASSERT(domain->alt_name != NULL); + realm = talloc_strdup(tmp_ctx, domain->alt_name); + } else { + struct winbindd_domain *our_domain = domain; + + + /* always give preference to the alt_name in our + primary domain if possible */ + + if ( !domain->primary ) + our_domain = find_our_domain(); + + if (our_domain->alt_name != NULL) { + realm = talloc_strdup(tmp_ctx, our_domain->alt_name ); + } else { + realm = talloc_strdup(tmp_ctx, lp_realm() ); + } + } + + if (realm == NULL) { + status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + goto out; + } + + status = ads_cached_connection_connect( + domain->alt_name, + domain->name, NULL, + password, + realm, + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME, + domain, + &domain->backend_data.ads_conn); + if (!ADS_ERR_OK(status)) { + /* if we get ECONNREFUSED then it might be a NT4 + server, fall back to MSRPC */ + if (status.error_type == ENUM_ADS_ERROR_SYSTEM && + status.err.rc == ECONNREFUSED) { + /* 'reconnect_methods' is the MS-RPC backend. */ + DBG_NOTICE("Trying MSRPC methods for domain '%s'\n", + domain->name); + domain->backend = &reconnect_methods; + } + goto out; + } + + *adsp = domain->backend_data.ads_conn; +out: + TALLOC_FREE(tmp_ctx); + SAFE_FREE(password); + + return status; +} + +/* Query display info for a realm. This is the basic user list fn */ +static NTSTATUS query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **prids) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = { "sAMAccountType", "objectSid", NULL }; + int count; + uint32_t *rids = NULL; + ADS_STATUS rc; + LDAPMessage *res = NULL; + LDAPMessage *msg = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + DEBUG(3,("ads: query_user_list\n")); + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("query_user_list: No incoming trust for domain %s\n", + domain->name)); + return NT_STATUS_OK; + } + + rc = ads_cached_connection(domain, &ads); + if (!ADS_ERR_OK(rc)) { + domain->last_status = NT_STATUS_SERVER_DISABLED; + goto done; + } + + rc = ads_search_retry(ads, &res, "(objectCategory=user)", attrs); + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("query_user_list ads_search: %s\n", ads_errstr(rc))); + status = ads_ntstatus(rc); + goto done; + } else if (!res) { + DEBUG(1,("query_user_list ads_search returned NULL res\n")); + goto done; + } + + count = ads_count_replies(ads, res); + if (count == 0) { + DEBUG(1,("query_user_list: No users found\n")); + goto done; + } + + rids = talloc_zero_array(mem_ctx, uint32_t, count); + if (rids == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + count = 0; + + for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) { + struct dom_sid user_sid; + uint32_t atype; + bool ok; + + ok = ads_pull_uint32(ads, msg, "sAMAccountType", &atype); + if (!ok) { + DBG_INFO("Object lacks sAMAccountType attribute\n"); + continue; + } + if (ds_atype_map(atype) != SID_NAME_USER) { + DBG_INFO("Not a user account? atype=0x%x\n", atype); + continue; + } + + if (!ads_pull_sid(ads, msg, "objectSid", &user_sid)) { + char *dn = ads_get_dn(ads, talloc_tos(), msg); + DBG_INFO("No sid for %s !?\n", dn); + TALLOC_FREE(dn); + continue; + } + + if (!dom_sid_in_domain(&domain->sid, &user_sid)) { + struct dom_sid_buf sidstr, domstr; + DBG_WARNING("Got sid %s in domain %s\n", + dom_sid_str_buf(&user_sid, &sidstr), + dom_sid_str_buf(&domain->sid, &domstr)); + continue; + } + + sid_split_rid(&user_sid, &rids[count]); + count += 1; + } + + rids = talloc_realloc(mem_ctx, rids, uint32_t, count); + if (prids != NULL) { + *prids = rids; + } else { + TALLOC_FREE(rids); + } + + status = NT_STATUS_OK; + + DBG_NOTICE("ads query_user_list gave %d entries\n", count); + +done: + ads_msgfree(ads, res); + return status; +} + +/* list all domain groups */ +static NTSTATUS enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"userPrincipalName", "sAMAccountName", + "name", "objectSid", NULL}; + int i, count; + ADS_STATUS rc; + LDAPMessage *res = NULL; + LDAPMessage *msg = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + const char *filter; + bool enum_dom_local_groups = False; + + *num_entries = 0; + + DEBUG(3,("ads: enum_dom_groups\n")); + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("enum_dom_groups: No incoming trust for domain %s\n", + domain->name)); + return NT_STATUS_OK; + } + + /* only grab domain local groups for our domain */ + if ( domain->active_directory && strequal(lp_realm(), domain->alt_name) ) { + enum_dom_local_groups = True; + } + + /* Workaround ADS LDAP bug present in MS W2K3 SP0 and W2K SP4 w/o + * rollup-fixes: + * + * According to Section 5.1(4) of RFC 2251 if a value of a type is it's + * default value, it MUST be absent. In case of extensible matching the + * "dnattr" boolean defaults to FALSE and so it must be only be present + * when set to TRUE. + * + * When it is set to FALSE and the OpenLDAP lib (correctly) encodes a + * filter using bitwise matching rule then the buggy AD fails to decode + * the extensible match. As a workaround set it to TRUE and thereby add + * the dnAttributes "dn" field to cope with those older AD versions. + * It should not harm and won't put any additional load on the AD since + * none of the dn components have a bitmask-attribute. + * + * Thanks to Ralf Haferkamp for input and testing - Guenther */ + + filter = talloc_asprintf(mem_ctx, "(&(objectCategory=group)" + "(&(groupType:dn:"ADS_LDAP_MATCHING_RULE_BIT_AND":=%d)" + "(!(groupType:dn:"ADS_LDAP_MATCHING_RULE_BIT_AND":=%d))))", + GROUP_TYPE_SECURITY_ENABLED, + enum_dom_local_groups ? GROUP_TYPE_BUILTIN_LOCAL_GROUP : GROUP_TYPE_RESOURCE_GROUP); + + if (filter == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = ads_cached_connection(domain, &ads); + if (!ADS_ERR_OK(rc)) { + domain->last_status = NT_STATUS_SERVER_DISABLED; + goto done; + } + + rc = ads_search_retry(ads, &res, filter, attrs); + if (!ADS_ERR_OK(rc)) { + status = ads_ntstatus(rc); + DEBUG(1,("enum_dom_groups ads_search: %s\n", ads_errstr(rc))); + goto done; + } else if (!res) { + DEBUG(1,("enum_dom_groups ads_search returned NULL res\n")); + goto done; + } + + count = ads_count_replies(ads, res); + if (count == 0) { + DEBUG(1,("enum_dom_groups: No groups found\n")); + goto done; + } + + (*info) = talloc_zero_array(mem_ctx, struct wb_acct_info, count); + if (!*info) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + i = 0; + + for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) { + char *name, *gecos; + struct dom_sid sid; + uint32_t rid; + + name = ads_pull_username(ads, (*info), msg); + gecos = ads_pull_string(ads, (*info), msg, "name"); + if (!ads_pull_sid(ads, msg, "objectSid", &sid)) { + DEBUG(1,("No sid for %s !?\n", name)); + continue; + } + + if (!sid_peek_check_rid(&domain->sid, &sid, &rid)) { + DEBUG(1,("No rid for %s !?\n", name)); + continue; + } + + (*info)[i].acct_name = name; + (*info)[i].acct_desc = gecos; + (*info)[i].rid = rid; + i++; + } + + (*num_entries) = i; + + status = NT_STATUS_OK; + + DEBUG(3,("ads enum_dom_groups gave %d entries\n", (*num_entries))); + +done: + if (res) + ads_msgfree(ads, res); + + return status; +} + +/* list all domain local groups */ +static NTSTATUS enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + /* + * This is a stub function only as we returned the domain + * local groups in enum_dom_groups() if the domain->native field + * was true. This is a simple performance optimization when + * using LDAP. + * + * if we ever need to enumerate domain local groups separately, + * then this optimization in enum_dom_groups() will need + * to be split out + */ + *num_entries = 0; + + return NT_STATUS_OK; +} + +/* convert a single name to a sid in a domain - use rpc methods */ +static NTSTATUS name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + uint32_t flags, + const char **pdom_name, + struct dom_sid *sid, + enum lsa_SidType *type) +{ + return msrpc_methods.name_to_sid(domain, mem_ctx, domain_name, name, + flags, pdom_name, sid, type); +} + +/* convert a domain SID to a user or group name - use rpc methods */ +static NTSTATUS sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + char **domain_name, + char **name, + enum lsa_SidType *type) +{ + return msrpc_methods.sid_to_name(domain, mem_ctx, sid, + domain_name, name, type); +} + +/* convert a list of rids to names - use rpc methods */ +static NTSTATUS rids_to_names(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + uint32_t *rids, + size_t num_rids, + char **domain_name, + char ***names, + enum lsa_SidType **types) +{ + return msrpc_methods.rids_to_names(domain, mem_ctx, sid, + rids, num_rids, + domain_name, names, types); +} + +/* Lookup groups a user is a member of - alternate method, for when + tokenGroups are not available. */ +static NTSTATUS lookup_usergroups_member(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *user_dn, + struct dom_sid *primary_group, + uint32_t *p_num_groups, struct dom_sid **user_sids) +{ + ADS_STATUS rc; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + int count; + LDAPMessage *res = NULL; + LDAPMessage *msg = NULL; + char *ldap_exp; + ADS_STRUCT *ads = NULL; + const char *group_attrs[] = {"objectSid", NULL}; + char *escaped_dn; + uint32_t num_groups = 0; + + DEBUG(3,("ads: lookup_usergroups_member\n")); + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("lookup_usergroups_members: No incoming trust for domain %s\n", + domain->name)); + return NT_STATUS_OK; + } + + rc = ads_cached_connection(domain, &ads); + if (!ADS_ERR_OK(rc)) { + domain->last_status = NT_STATUS_SERVER_DISABLED; + goto done; + } + + if (!(escaped_dn = escape_ldap_string(talloc_tos(), user_dn))) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + ldap_exp = talloc_asprintf(mem_ctx, + "(&(member=%s)(objectCategory=group)" + "(groupType:dn:"ADS_LDAP_MATCHING_RULE_BIT_AND":=%d))", + escaped_dn, + GROUP_TYPE_SECURITY_ENABLED); + if (!ldap_exp) { + DEBUG(1,("lookup_usergroups(dn=%s) asprintf failed!\n", user_dn)); + TALLOC_FREE(escaped_dn); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + TALLOC_FREE(escaped_dn); + + rc = ads_search_retry(ads, &res, ldap_exp, group_attrs); + + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("lookup_usergroups ads_search member=%s: %s\n", user_dn, ads_errstr(rc))); + return ads_ntstatus(rc); + } else if (!res) { + DEBUG(1,("lookup_usergroups ads_search returned NULL res\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + + count = ads_count_replies(ads, res); + + *user_sids = NULL; + num_groups = 0; + + /* always add the primary group to the sid array */ + status = add_sid_to_array(mem_ctx, primary_group, user_sids, + &num_groups); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (count > 0) { + for (msg = ads_first_entry(ads, res); msg; + msg = ads_next_entry(ads, msg)) { + struct dom_sid group_sid; + + if (!ads_pull_sid(ads, msg, "objectSid", &group_sid)) { + DEBUG(1,("No sid for this group ?!?\n")); + continue; + } + + /* ignore Builtin groups from ADS - Guenther */ + if (sid_check_is_in_builtin(&group_sid)) { + continue; + } + + status = add_sid_to_array(mem_ctx, &group_sid, + user_sids, &num_groups); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + } + + } + + *p_num_groups = num_groups; + status = (user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY; + + DEBUG(3,("ads lookup_usergroups (member) succeeded for dn=%s\n", user_dn)); +done: + if (res) + ads_msgfree(ads, res); + + return status; +} + +/* Lookup groups a user is a member of - alternate method, for when + tokenGroups are not available. */ +static NTSTATUS lookup_usergroups_memberof(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *user_dn, + struct dom_sid *primary_group, + uint32_t *p_num_groups, + struct dom_sid **user_sids) +{ + ADS_STATUS rc; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"memberOf", NULL}; + uint32_t num_groups = 0; + struct dom_sid *group_sids = NULL; + size_t i; + char **strings = NULL; + size_t num_strings = 0, num_sids = 0; + + + DEBUG(3,("ads: lookup_usergroups_memberof\n")); + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("lookup_usergroups_memberof: No incoming trust for " + "domain %s\n", domain->name)); + return NT_STATUS_OK; + } + + rc = ads_cached_connection(domain, &ads); + if (!ADS_ERR_OK(rc)) { + domain->last_status = NT_STATUS_SERVER_DISABLED; + return NT_STATUS_UNSUCCESSFUL; + } + + rc = ads_search_retry_extended_dn_ranged(ads, mem_ctx, user_dn, attrs, + ADS_EXTENDED_DN_HEX_STRING, + &strings, &num_strings); + + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("lookup_usergroups_memberof ads_search " + "member=%s: %s\n", user_dn, ads_errstr(rc))); + return ads_ntstatus(rc); + } + + *user_sids = NULL; + num_groups = 0; + + /* always add the primary group to the sid array */ + status = add_sid_to_array(mem_ctx, primary_group, user_sids, + &num_groups); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + group_sids = talloc_zero_array(mem_ctx, struct dom_sid, num_strings + 1); + if (!group_sids) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + for (i=0; i<num_strings; i++) { + rc = ads_get_sid_from_extended_dn(mem_ctx, strings[i], + ADS_EXTENDED_DN_HEX_STRING, + &(group_sids)[i]); + if (!ADS_ERR_OK(rc)) { + /* ignore members without SIDs */ + if (NT_STATUS_EQUAL(ads_ntstatus(rc), + NT_STATUS_NOT_FOUND)) { + continue; + } + else { + status = ads_ntstatus(rc); + goto done; + } + } + num_sids++; + } + + if (i == 0) { + DEBUG(1,("No memberOf for this user?!?\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + for (i=0; i<num_sids; i++) { + + /* ignore Builtin groups from ADS - Guenther */ + if (sid_check_is_in_builtin(&group_sids[i])) { + continue; + } + + status = add_sid_to_array(mem_ctx, &group_sids[i], user_sids, + &num_groups); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + } + + *p_num_groups = num_groups; + status = (*user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY; + + DEBUG(3,("ads lookup_usergroups (memberof) succeeded for dn=%s\n", + user_dn)); + +done: + TALLOC_FREE(strings); + TALLOC_FREE(group_sids); + + return status; +} + + +/* Lookup groups a user is a member of. */ +static NTSTATUS lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + uint32_t *p_num_groups, struct dom_sid **user_sids) +{ + ADS_STRUCT *ads = NULL; + const char *attrs[] = {"tokenGroups", "primaryGroupID", NULL}; + ADS_STATUS rc; + int count; + LDAPMessage *msg = NULL; + char *user_dn = NULL; + struct dom_sid *sids; + int i; + struct dom_sid primary_group; + uint32_t primary_group_rid; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + uint32_t num_groups = 0; + struct dom_sid_buf buf; + + DEBUG(3,("ads: lookup_usergroups\n")); + *p_num_groups = 0; + + status = lookup_usergroups_cached(mem_ctx, sid, + p_num_groups, user_sids); + if (NT_STATUS_IS_OK(status)) { + return NT_STATUS_OK; + } + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("lookup_usergroups: No incoming trust for domain %s\n", + domain->name)); + + /* Tell the cache manager not to remember this one */ + + return NT_STATUS_SYNCHRONIZATION_REQUIRED; + } + + rc = ads_cached_connection(domain, &ads); + if (!ADS_ERR_OK(rc)) { + domain->last_status = NT_STATUS_SERVER_DISABLED; + status = NT_STATUS_SERVER_DISABLED; + goto done; + } + + rc = ads_search_retry_sid(ads, &msg, sid, attrs); + + if (!ADS_ERR_OK(rc)) { + status = ads_ntstatus(rc); + DEBUG(1, ("lookup_usergroups(sid=%s) ads_search tokenGroups: " + "%s\n", + dom_sid_str_buf(sid, &buf), + ads_errstr(rc))); + goto done; + } + + count = ads_count_replies(ads, msg); + if (count != 1) { + status = NT_STATUS_UNSUCCESSFUL; + DEBUG(1,("lookup_usergroups(sid=%s) ads_search tokenGroups: " + "invalid number of results (count=%d)\n", + dom_sid_str_buf(sid, &buf), + count)); + goto done; + } + + if (!msg) { + DEBUG(1,("lookup_usergroups(sid=%s) ads_search tokenGroups: NULL msg\n", + dom_sid_str_buf(sid, &buf))); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + user_dn = ads_get_dn(ads, mem_ctx, msg); + if (user_dn == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + if (!ads_pull_uint32(ads, msg, "primaryGroupID", &primary_group_rid)) { + DEBUG(1,("%s: No primary group for sid=%s !?\n", + domain->name, + dom_sid_str_buf(sid, &buf))); + goto done; + } + + sid_compose(&primary_group, &domain->sid, primary_group_rid); + + count = ads_pull_sids(ads, mem_ctx, msg, "tokenGroups", &sids); + + /* there must always be at least one group in the token, + unless we are talking to a buggy Win2k server */ + + /* actually this only happens when the machine account has no read + * permissions on the tokenGroup attribute - gd */ + + if (count == 0) { + + /* no tokenGroups */ + + /* lookup what groups this user is a member of by DN search on + * "memberOf" */ + + status = lookup_usergroups_memberof(domain, mem_ctx, user_dn, + &primary_group, + &num_groups, user_sids); + *p_num_groups = num_groups; + if (NT_STATUS_IS_OK(status)) { + goto done; + } + + /* lookup what groups this user is a member of by DN search on + * "member" */ + + status = lookup_usergroups_member(domain, mem_ctx, user_dn, + &primary_group, + &num_groups, user_sids); + *p_num_groups = num_groups; + goto done; + } + + *user_sids = NULL; + num_groups = 0; + + status = add_sid_to_array(mem_ctx, &primary_group, user_sids, + &num_groups); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + for (i=0;i<count;i++) { + + /* ignore Builtin groups from ADS - Guenther */ + if (sid_check_is_in_builtin(&sids[i])) { + continue; + } + + status = add_sid_to_array_unique(mem_ctx, &sids[i], + user_sids, &num_groups); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + } + + *p_num_groups = (uint32_t)num_groups; + status = (*user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY; + + DEBUG(3,("ads lookup_usergroups (tokenGroups) succeeded for sid=%s\n", + dom_sid_str_buf(sid, &buf))); +done: + TALLOC_FREE(user_dn); + ads_msgfree(ads, msg); + return status; +} + +/* Lookup aliases a user is member of - use rpc methods */ +static NTSTATUS lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, const struct dom_sid *sids, + uint32_t *num_aliases, uint32_t **alias_rids) +{ + return msrpc_methods.lookup_useraliases(domain, mem_ctx, num_sids, sids, + num_aliases, alias_rids); +} + +static NTSTATUS add_primary_group_members( + ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, uint32_t rid, + char ***all_members, size_t *num_all_members) +{ + char *filter; + NTSTATUS status = NT_STATUS_NO_MEMORY; + ADS_STATUS rc; + const char *attrs[] = { "dn", NULL }; + LDAPMessage *res = NULL; + LDAPMessage *msg; + char **members; + size_t num_members; + ads_control args; + + filter = talloc_asprintf( + mem_ctx, "(&(objectCategory=user)(primaryGroupID=%u))", + (unsigned)rid); + if (filter == NULL) { + goto done; + } + + args.control = ADS_EXTENDED_DN_OID; + args.val = ADS_EXTENDED_DN_HEX_STRING; + args.critical = True; + + rc = ads_do_search_all_args(ads, ads->config.bind_path, + LDAP_SCOPE_SUBTREE, filter, attrs, &args, + &res); + + if (!ADS_ERR_OK(rc)) { + status = ads_ntstatus(rc); + DEBUG(1,("%s: ads_search: %s\n", __func__, ads_errstr(rc))); + goto done; + } + if (res == NULL) { + DEBUG(1,("%s: ads_search returned NULL res\n", __func__)); + goto done; + } + + num_members = ads_count_replies(ads, res); + + DEBUG(10, ("%s: Got %ju primary group members\n", __func__, + (uintmax_t)num_members)); + + if (num_members == 0) { + status = NT_STATUS_OK; + goto done; + } + + members = talloc_realloc(mem_ctx, *all_members, char *, + *num_all_members + num_members); + if (members == NULL) { + DEBUG(1, ("%s: talloc_realloc failed\n", __func__)); + goto done; + } + *all_members = members; + + for (msg = ads_first_entry(ads, res); msg != NULL; + msg = ads_next_entry(ads, msg)) { + char *dn; + + dn = ads_get_dn(ads, members, msg); + if (dn == NULL) { + DEBUG(1, ("%s: ads_get_dn failed\n", __func__)); + continue; + } + + members[*num_all_members] = dn; + *num_all_members += 1; + } + + status = NT_STATUS_OK; +done: + if (res != NULL) { + ads_msgfree(ads, res); + } + TALLOC_FREE(filter); + return status; +} + +/* + find the members of a group, given a group rid and domain + */ +static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem, char ***names, + uint32_t **name_types) +{ + ADS_STATUS rc; + ADS_STRUCT *ads = NULL; + char *ldap_exp; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + char *sidbinstr; + char **members = NULL; + size_t i; + size_t num_members = 0; + ads_control args; + struct dom_sid *sid_mem_nocache = NULL; + char **names_nocache = NULL; + enum lsa_SidType *name_types_nocache = NULL; + char **domains_nocache = NULL; /* only needed for rpccli_lsa_lookup_sids */ + uint32_t num_nocache = 0; + TALLOC_CTX *tmp_ctx = NULL; + uint32_t rid; + struct dom_sid_buf buf; + + DEBUG(10,("ads: lookup_groupmem %s sid=%s\n", domain->name, + dom_sid_str_buf(group_sid, &buf))); + + *num_names = 0; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + DEBUG(1, ("ads: lookup_groupmem: talloc failed\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + if (!sid_peek_rid(group_sid, &rid)) { + DEBUG(1, ("%s: sid_peek_rid failed\n", __func__)); + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("lookup_groupmem: No incoming trust for domain %s\n", + domain->name)); + return NT_STATUS_OK; + } + + rc = ads_cached_connection(domain, &ads); + if (!ADS_ERR_OK(rc)) { + domain->last_status = NT_STATUS_SERVER_DISABLED; + goto done; + } + + if ((sidbinstr = ldap_encode_ndr_dom_sid(talloc_tos(), group_sid)) == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + /* search for all members of the group */ + ldap_exp = talloc_asprintf(tmp_ctx, "(objectSid=%s)", sidbinstr); + TALLOC_FREE(sidbinstr); + if (ldap_exp == NULL) { + DEBUG(1, ("ads: lookup_groupmem: talloc_asprintf for ldap_exp failed!\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + args.control = ADS_EXTENDED_DN_OID; + args.val = ADS_EXTENDED_DN_HEX_STRING; + args.critical = True; + + rc = ads_ranged_search(ads, tmp_ctx, LDAP_SCOPE_SUBTREE, ads->config.bind_path, + ldap_exp, &args, "member", &members, &num_members); + + if (!ADS_ERR_OK(rc)) { + DEBUG(0,("ads_ranged_search failed with: %s\n", ads_errstr(rc))); + status = NT_STATUS_UNSUCCESSFUL; + goto done; + } + + DEBUG(10, ("ads lookup_groupmem: got %d sids via extended dn call\n", (int)num_members)); + + status = add_primary_group_members(ads, mem_ctx, rid, + &members, &num_members); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("%s: add_primary_group_members failed: %s\n", + __func__, nt_errstr(status))); + goto done; + } + + DEBUG(10, ("%s: Got %d sids after adding primary group members\n", + __func__, (int)num_members)); + + /* Now that we have a list of sids, we need to get the + * lists of names and name_types belonging to these sids. + * even though conceptually not quite clean, we use the + * RPC call lsa_lookup_sids for this since it can handle a + * list of sids. ldap calls can just resolve one sid at a time. + * + * At this stage, the sids are still hidden in the exetended dn + * member output format. We actually do a little better than + * stated above: In extracting the sids from the member strings, + * we try to resolve as many sids as possible from the + * cache. Only the rest is passed to the lsa_lookup_sids call. */ + + if (num_members) { + (*sid_mem) = talloc_zero_array(mem_ctx, struct dom_sid, num_members); + (*names) = talloc_zero_array(mem_ctx, char *, num_members); + (*name_types) = talloc_zero_array(mem_ctx, uint32_t, num_members); + (sid_mem_nocache) = talloc_zero_array(tmp_ctx, struct dom_sid, num_members); + + if ((members == NULL) || (*sid_mem == NULL) || + (*names == NULL) || (*name_types == NULL) || + (sid_mem_nocache == NULL)) + { + DEBUG(1, ("ads: lookup_groupmem: talloc failed\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + } + else { + (*sid_mem) = NULL; + (*names) = NULL; + (*name_types) = NULL; + } + + for (i=0; i<num_members; i++) { + enum lsa_SidType name_type; + char *name, *domain_name; + struct dom_sid sid; + + rc = ads_get_sid_from_extended_dn(tmp_ctx, members[i], args.val, + &sid); + if (!ADS_ERR_OK(rc)) { + if (NT_STATUS_EQUAL(ads_ntstatus(rc), + NT_STATUS_NOT_FOUND)) { + /* Group members can be objects, like Exchange + * Public Folders, that don't have a SID. Skip + * them. */ + continue; + } + else { + status = ads_ntstatus(rc); + goto done; + } + } + if (lookup_cached_sid(mem_ctx, &sid, &domain_name, &name, + &name_type)) { + DEBUG(10,("ads: lookup_groupmem: got sid %s from " + "cache\n", + dom_sid_str_buf(&sid, &buf))); + sid_copy(&(*sid_mem)[*num_names], &sid); + (*names)[*num_names] = fill_domain_username_talloc( + *names, + domain_name, + name, + true); + + (*name_types)[*num_names] = name_type; + (*num_names)++; + } + else { + DEBUG(10, ("ads: lookup_groupmem: sid %s not found in " + "cache\n", + dom_sid_str_buf(&sid, &buf))); + sid_copy(&(sid_mem_nocache)[num_nocache], &sid); + num_nocache++; + } + } + + DEBUG(10, ("ads: lookup_groupmem: %d sids found in cache, " + "%d left for lsa_lookupsids\n", *num_names, num_nocache)); + + /* handle sids not resolved from cache by lsa_lookup_sids */ + if (num_nocache > 0) { + + status = winbindd_lookup_sids(tmp_ctx, + domain, + num_nocache, + sid_mem_nocache, + &domains_nocache, + &names_nocache, + &name_types_nocache); + + if (!(NT_STATUS_IS_OK(status) || + NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED) || + NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))) + { + DEBUG(1, ("lsa_lookupsids call failed with %s " + "- retrying...\n", nt_errstr(status))); + + status = winbindd_lookup_sids(tmp_ctx, + domain, + num_nocache, + sid_mem_nocache, + &domains_nocache, + &names_nocache, + &name_types_nocache); + } + + if (NT_STATUS_IS_OK(status) || + NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) + { + /* Copy the entries over from the "_nocache" arrays + * to the result arrays, skipping the gaps the + * lookup_sids call left. */ + for (i=0; i < num_nocache; i++) { + if (((names_nocache)[i] != NULL) && + ((name_types_nocache)[i] != SID_NAME_UNKNOWN)) + { + sid_copy(&(*sid_mem)[*num_names], + &sid_mem_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)++; + } + } + } + else if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + DEBUG(10, ("lookup_groupmem: lsa_lookup_sids could " + "not map any SIDs at all.\n")); + /* Don't handle this as an error here. + * There is nothing left to do with respect to the + * overall result... */ + } + else if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("lookup_groupmem: Error looking up %d " + "sids via rpc_lsa_lookup_sids: %s\n", + (int)num_members, nt_errstr(status))); + goto done; + } + } + + status = NT_STATUS_OK; + DEBUG(3,("ads lookup_groupmem for sid=%s succeeded\n", + dom_sid_str_buf(group_sid, &buf))); + +done: + + TALLOC_FREE(tmp_ctx); + + return status; +} + +static NTSTATUS lookup_aliasmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + enum lsa_SidType type, + uint32_t *num_sids, + struct dom_sid **sids) +{ + char **names = NULL; + uint32_t *name_types = NULL; + struct dom_sid_buf buf; + + DBG_DEBUG("ads: lookup_aliasmem %s sid=%s\n", + domain->name, + dom_sid_str_buf(sid, &buf)); + /* Search for alias and group membership uses the same LDAP command. */ + return lookup_groupmem(domain, + mem_ctx, + sid, + type, + num_sids, + sids, + &names, + &name_types); +} + +/* find the lockout policy of a domain - use rpc methods */ +static NTSTATUS lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *policy) +{ + return msrpc_methods.lockout_policy(domain, mem_ctx, policy); +} + +/* find the password policy of a domain - use rpc methods */ +static NTSTATUS password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *policy) +{ + return msrpc_methods.password_policy(domain, mem_ctx, policy); +} + +/* get a list of trusted domains */ +static NTSTATUS trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *trusts) +{ + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + WERROR werr; + uint32_t i; + uint32_t flags; + struct rpc_pipe_client *cli; + struct dcerpc_binding_handle *b; + + DEBUG(3,("ads: trusted_domains\n")); + + ZERO_STRUCTP(trusts); + + /* If this is our primary domain or a root in our forest, + query for all trusts. If not, then just look for domain + trusts in the target forest */ + + if (domain->primary || domain_is_forest_root(domain)) { + flags = NETR_TRUST_FLAG_OUTBOUND | + NETR_TRUST_FLAG_INBOUND | + NETR_TRUST_FLAG_IN_FOREST; + } else { + flags = NETR_TRUST_FLAG_IN_FOREST; + } + + result = cm_connect_netlogon(domain, &cli); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(5, ("trusted_domains: Could not open a connection to %s " + "for PIPE_NETLOGON (%s)\n", + domain->name, nt_errstr(result))); + return NT_STATUS_UNSUCCESSFUL; + } + + b = cli->binding_handle; + + result = dcerpc_netr_DsrEnumerateDomainTrusts(b, mem_ctx, + cli->desthost, + flags, + trusts, + &werr); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + if (!W_ERROR_IS_OK(werr)) { + return werror_to_ntstatus(werr); + } + if (trusts->count == 0) { + return NT_STATUS_OK; + } + + /* Copy across names and sids */ + + for (i = 0; i < trusts->count; i++) { + struct netr_DomainTrust *trust = &trusts->array[i]; + struct winbindd_domain d; + + ZERO_STRUCT(d); + + /* + * drop external trusts if this is not our primary + * domain. This means that the returned number of + * domains may be less that the ones actually trusted + * by the DC. + */ + + if ((trust->trust_attributes + & LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN) && + !domain->primary ) + { + DEBUG(10,("trusted_domains: Skipping external trusted " + "domain %s because it is outside of our " + "primary domain\n", + trust->netbios_name)); + continue; + } + + /* add to the trusted domain cache */ + + d.name = discard_const_p(char, trust->netbios_name); + d.alt_name = discard_const_p(char, trust->dns_name); + + if (trust->sid) { + sid_copy(&d.sid, trust->sid); + } else { + sid_copy(&d.sid, &global_sid_NULL); + } + + if ( domain->primary ) { + DEBUG(10,("trusted_domains(ads): Searching " + "trusted domain list of %s and storing " + "trust flags for domain %s\n", + domain->name, d.alt_name)); + + d.domain_flags = trust->trust_flags; + d.domain_type = trust->trust_type; + d.domain_trust_attribs = trust->trust_attributes; + + wcache_tdc_add_domain( &d ); + } else if (domain_is_forest_root(domain)) { + /* Check if we already have this record. If + * we are following our forest root that is not + * our primary domain, we want to keep trust + * flags from the perspective of our primary + * domain not our forest root. */ + struct winbindd_tdc_domain *exist = NULL; + + exist = wcache_tdc_fetch_domain( + talloc_tos(), trust->netbios_name); + if (!exist) { + DEBUG(10,("trusted_domains(ads): Searching " + "trusted domain list of %s and " + "storing trust flags for domain " + "%s\n", domain->name, d.alt_name)); + d.domain_flags = trust->trust_flags; + d.domain_type = trust->trust_type; + d.domain_trust_attribs = + trust->trust_attributes; + + wcache_tdc_add_domain( &d ); + } + TALLOC_FREE(exist); + } else { + /* This gets a little tricky. If we are + following a transitive forest trust, then + innerit the flags, type, and attribs from + the domain we queried to make sure we don't + record the view of the trust from the wrong + side. Always view it from the side of our + primary domain. --jerry */ + struct winbindd_tdc_domain *parent = NULL; + + DEBUG(10,("trusted_domains(ads): Searching " + "trusted domain list of %s and inheriting " + "trust flags for domain %s\n", + domain->name, d.alt_name)); + + parent = wcache_tdc_fetch_domain(talloc_tos(), + domain->name); + if (parent) { + d.domain_flags = parent->trust_flags; + d.domain_type = parent->trust_type; + d.domain_trust_attribs = parent->trust_attribs; + } else { + d.domain_flags = domain->domain_flags; + d.domain_type = domain->domain_type; + d.domain_trust_attribs = + domain->domain_trust_attribs; + } + TALLOC_FREE(parent); + + /* + * We need to pass the modified properties + * to the caller. + */ + trust->trust_flags = d.domain_flags; + trust->trust_type = d.domain_type; + trust->trust_attributes = d.domain_trust_attribs; + + wcache_tdc_add_domain( &d ); + } + } + return result; +} + +/* the ADS backend methods are exposed via this structure */ +struct winbindd_methods ads_methods = { + True, + query_user_list, + enum_dom_groups, + enum_local_groups, + name_to_sid, + sid_to_name, + rids_to_names, + lookup_usergroups, + lookup_useraliases, + lookup_groupmem, + lookup_aliasmem, + lockout_policy, + password_policy, + trusted_domains, +}; + +#endif diff --git a/source3/winbindd/winbindd_ads.h b/source3/winbindd/winbindd_ads.h new file mode 100644 index 0000000..0fd9774 --- /dev/null +++ b/source3/winbindd/winbindd_ads.h @@ -0,0 +1,34 @@ +/* + Unix SMB/CIFS implementation. + + Winbind ADS backend functions + + Copyright (C) Volker Lendecke 2017 + + 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 __WINBINDD_ADS_H__ +#define __WINBINDD_ADS_H__ + + +#include "ads.h" + +extern struct winbindd_methods ads_methods; + +ADS_STATUS ads_idmap_cached_connection(const char *dom_name, + TALLOC_CTX *mem_ctx, + ADS_STRUCT **adsp); + +#endif diff --git a/source3/winbindd/winbindd_allocate_gid.c b/source3/winbindd/winbindd_allocate_gid.c new file mode 100644 index 0000000..2841d96 --- /dev/null +++ b/source3/winbindd/winbindd_allocate_gid.c @@ -0,0 +1,121 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_ALLOCATE_GID + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_allocate_gid_state { + struct tevent_context *ev; + uint64_t gid; +}; + +static void winbindd_allocate_gid_initialized(struct tevent_req *subreq); +static void winbindd_allocate_gid_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_allocate_gid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_allocate_gid_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_allocate_gid_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + DEBUG(3, ("allocate_gid\n")); + + subreq = wb_parent_idmap_setup_send(state, ev); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, winbindd_allocate_gid_initialized, req); + return req; +} + +static void winbindd_allocate_gid_initialized(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_allocate_gid_state *state = tevent_req_data( + req, struct winbindd_allocate_gid_state); + NTSTATUS status; + const struct wb_parent_idmap_config *cfg = NULL; + struct dcerpc_binding_handle *child_binding_handle = NULL; + + status = wb_parent_idmap_setup_recv(subreq, &cfg); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (cfg->num_doms == 0) { + /* + * idmap_tdb also returns UNSUCCESSFUL if a range is full + */ + tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL); + return; + } + + child_binding_handle = idmap_child_handle(); + + subreq = dcerpc_wbint_AllocateGid_send( + state, state->ev, child_binding_handle, &state->gid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_allocate_gid_done, req); +} + +static void winbindd_allocate_gid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_allocate_gid_state *state = tevent_req_data( + req, struct winbindd_allocate_gid_state); + NTSTATUS status, result; + + status = dcerpc_wbint_AllocateGid_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_allocate_gid_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_allocate_gid_state *state = tevent_req_data( + req, struct winbindd_allocate_gid_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + DEBUG(5, ("Could not allocate gid: %s\n", nt_errstr(status))); + return status; + } + response->data.gid = state->gid; + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_allocate_uid.c b/source3/winbindd/winbindd_allocate_uid.c new file mode 100644 index 0000000..64711f1 --- /dev/null +++ b/source3/winbindd/winbindd_allocate_uid.c @@ -0,0 +1,122 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_ALLOCATE_UID + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_allocate_uid_state { + struct tevent_context *ev; + uint64_t uid; +}; + +static void winbindd_allocate_uid_initialized(struct tevent_req *subreq); +static void winbindd_allocate_uid_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_allocate_uid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_allocate_uid_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_allocate_uid_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + DEBUG(3, ("allocate_uid\n")); + + subreq = wb_parent_idmap_setup_send(state, ev); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_allocate_uid_initialized, req); + return req; +} + +static void winbindd_allocate_uid_initialized(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct dcerpc_binding_handle *child_binding_handle = NULL; + struct winbindd_allocate_uid_state *state = tevent_req_data( + req, struct winbindd_allocate_uid_state); + const struct wb_parent_idmap_config *cfg = NULL; + NTSTATUS status; + + status = wb_parent_idmap_setup_recv(subreq, &cfg); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (cfg->num_doms == 0) { + /* + * idmap_tdb also returns UNSUCCESSFUL if a range is full + */ + tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL); + return; + } + + child_binding_handle = idmap_child_handle(); + + subreq = dcerpc_wbint_AllocateUid_send(state, + state->ev, + child_binding_handle, + &state->uid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_allocate_uid_done, req); +} + +static void winbindd_allocate_uid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_allocate_uid_state *state = tevent_req_data( + req, struct winbindd_allocate_uid_state); + NTSTATUS status, result; + + status = dcerpc_wbint_AllocateUid_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_allocate_uid_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_allocate_uid_state *state = tevent_req_data( + req, struct winbindd_allocate_uid_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + DEBUG(5, ("Could not allocate uid: %s\n", nt_errstr(status))); + return status; + } + response->data.uid = state->uid; + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_cache.c b/source3/winbindd/winbindd_cache.c new file mode 100644 index 0000000..ca2341e --- /dev/null +++ b/source3/winbindd/winbindd_cache.c @@ -0,0 +1,4930 @@ +/* + Unix SMB/CIFS implementation. + + Winbind cache backend functions + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Gerald Carter 2003-2007 + Copyright (C) Volker Lendecke 2005 + Copyright (C) Guenther Deschner 2005 + Copyright (C) Michael Adam 2007 + + 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 "system/filesys.h" +#include "winbindd.h" +#include "tdb_validate.h" +#include "../libcli/auth/libcli_auth.h" +#include "../librpc/gen_ndr/ndr_winbind.h" +#include "ads.h" +#include "nss_info.h" +#include "../libcli/security/security.h" +#include "passdb/machine_sid.h" +#include "util_tdb.h" +#include "libsmb/samlogon_cache.h" +#include "lib/namemap_cache.h" +#include "lib/util/string_wrappers.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +#define WINBINDD_CACHE_VER1 1 /* initial db version */ +#define WINBINDD_CACHE_VER2 2 /* second version with timeouts for NDR entries */ + +#define WINBINDD_CACHE_VERSION WINBINDD_CACHE_VER2 +#define WINBINDD_CACHE_VERSION_KEYSTR "WINBINDD_CACHE_VERSION" + +extern struct winbindd_methods reconnect_methods; +#ifdef HAVE_ADS +extern struct winbindd_methods reconnect_ads_methods; +#endif +extern struct winbindd_methods builtin_passdb_methods; +extern struct winbindd_methods sam_passdb_methods; + +static void wcache_flush_cache(void); + +static bool opt_nocache = False; + +/* + * JRA. KEEP THIS LIST UP TO DATE IF YOU ADD CACHE ENTRIES. + * Here are the list of entry types that are *not* stored + * as form struct cache_entry in the cache. + */ + +static const char *non_centry_keys[] = { + "SEQNUM/", + "WINBINDD_OFFLINE", + WINBINDD_CACHE_VERSION_KEYSTR, + NULL +}; + +bool winbindd_use_idmap_cache(void) +{ + return !opt_nocache; +} + +bool winbindd_use_cache(void) +{ + return !opt_nocache; +} + +void winbindd_set_use_cache(bool use_cache) +{ + opt_nocache = !use_cache; +} + +void winbindd_flush_caches(void) +{ + /* We need to invalidate cached user list entries on a SIGHUP + otherwise cached access denied errors due to restrict anonymous + hang around until the sequence number changes. */ + + if (!wcache_invalidate_cache()) { + DBG_ERR("invalidating the cache failed; revalidate the cache\n"); + if (!winbindd_cache_validate_and_initialize()) { + exit(1); + } + } +} + +/************************************************************************ + Is this key a non-centry type ? +************************************************************************/ + +static bool is_non_centry_key(TDB_DATA kbuf) +{ + int i; + + if (kbuf.dptr == NULL || kbuf.dsize == 0) { + return false; + } + for (i = 0; non_centry_keys[i] != NULL; i++) { + size_t namelen = strlen(non_centry_keys[i]); + if (kbuf.dsize < namelen) { + continue; + } + if (strncmp(non_centry_keys[i], (const char *)kbuf.dptr, namelen) == 0) { + return true; + } + } + return false; +} + +struct winbind_cache { + TDB_CONTEXT *tdb; +}; + +struct cache_entry { + NTSTATUS status; + uint32_t sequence_number; + uint64_t timeout; + uint8_t *data; + uint32_t len, ofs; +}; + +void (*smb_panic_fn)(const char *const why) = smb_panic; + +static struct winbind_cache *wcache; + +static char *wcache_path(void) +{ + /* + * Data needs to be kept persistent in state directory for + * running with "winbindd offline logon". + */ + return state_path(talloc_tos(), "winbindd_cache.tdb"); +} + +static void winbindd_domain_init_backend(struct winbindd_domain *domain) +{ + if (domain->backend != NULL) { + return; + } + + if (domain->internal) { + domain->backend = &builtin_passdb_methods; + } + + if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) { + domain->initialized = true; + } + + if (strequal(domain->name, get_global_sam_name()) && + sid_check_is_our_sam(&domain->sid)) + { + domain->backend = &sam_passdb_methods; + } + + if (!domain->initialized) { + /* We do not need a connection to an RW DC for cache operation */ + init_dc_connection(domain, false); + } + +#ifdef HAVE_ADS + if (domain->backend == NULL) { + struct winbindd_domain *our_domain = domain; + + /* find our domain first so we can figure out if we + are joined to a kerberized domain */ + + if (!domain->primary) { + our_domain = find_our_domain(); + } + + if ((our_domain->active_directory || IS_DC) + && domain->active_directory + && !lp_winbind_rpc_only()) + { + DBG_INFO("Setting ADS methods for domain %s\n", + domain->name); + domain->backend = &reconnect_ads_methods; + } + } +#endif /* HAVE_ADS */ + + if (domain->backend == NULL) { + DBG_INFO("Setting MS-RPC methods for domain %s\n", domain->name); + domain->backend = &reconnect_methods; + } +} + +/* get the winbind_cache structure */ +static struct winbind_cache *get_cache(struct winbindd_domain *domain) +{ + struct winbind_cache *ret = wcache; + + winbindd_domain_init_backend(domain); + + if (ret != NULL) { + return ret; + } + + ret = SMB_XMALLOC_P(struct winbind_cache); + ZERO_STRUCTP(ret); + + wcache = ret; + wcache_flush_cache(); + + return ret; +} + +/* + free a centry structure +*/ +static void centry_free(struct cache_entry *centry) +{ + if (!centry) + return; + SAFE_FREE(centry->data); + free(centry); +} + +static bool centry_check_bytes(struct cache_entry *centry, size_t nbytes) +{ + if (centry->len - centry->ofs < nbytes) { + DBG_ERR("centry corruption? needed %u bytes, have %d\n", + (unsigned int)nbytes, + centry->len - centry->ofs); + return false; + } + return true; +} + +/* + pull a uint64_t from a cache entry +*/ +static uint64_t centry_uint64_t(struct cache_entry *centry) +{ + uint64_t ret; + + if (!centry_check_bytes(centry, 8)) { + smb_panic_fn("centry_uint64_t"); + } + ret = BVAL(centry->data, centry->ofs); + centry->ofs += 8; + return ret; +} + +/* + pull a uint32_t from a cache entry +*/ +static uint32_t centry_uint32(struct cache_entry *centry) +{ + uint32_t ret; + + if (!centry_check_bytes(centry, 4)) { + smb_panic_fn("centry_uint32"); + } + ret = IVAL(centry->data, centry->ofs); + centry->ofs += 4; + return ret; +} + +/* + pull a uint16_t from a cache entry +*/ +static uint16_t centry_uint16(struct cache_entry *centry) +{ + uint16_t ret; + if (!centry_check_bytes(centry, 2)) { + smb_panic_fn("centry_uint16"); + } + ret = SVAL(centry->data, centry->ofs); + centry->ofs += 2; + return ret; +} + +/* + pull a uint8_t from a cache entry +*/ +static uint8_t centry_uint8(struct cache_entry *centry) +{ + uint8_t ret; + if (!centry_check_bytes(centry, 1)) { + smb_panic_fn("centry_uint8"); + } + ret = CVAL(centry->data, centry->ofs); + centry->ofs += 1; + return ret; +} + +/* + pull a NTTIME from a cache entry +*/ +static NTTIME centry_nttime(struct cache_entry *centry) +{ + NTTIME ret; + if (!centry_check_bytes(centry, 8)) { + smb_panic_fn("centry_nttime"); + } + ret = IVAL(centry->data, centry->ofs); + centry->ofs += 4; + ret += (uint64_t)IVAL(centry->data, centry->ofs) << 32; + centry->ofs += 4; + return ret; +} + +/* + pull a time_t from a cache entry. time_t stored portably as a 64-bit time. +*/ +static time_t centry_time(struct cache_entry *centry) +{ + return (time_t)centry_nttime(centry); +} + +/* pull a string from a cache entry, using the supplied + talloc context +*/ +static char *centry_string(struct cache_entry *centry, TALLOC_CTX *mem_ctx) +{ + uint32_t len; + char *ret; + + len = centry_uint8(centry); + + if (len == 0xFF) { + /* a deliberate NULL string */ + return NULL; + } + + if (!centry_check_bytes(centry, (size_t)len)) { + smb_panic_fn("centry_string"); + } + + ret = talloc_array(mem_ctx, char, len+1); + if (!ret) { + smb_panic_fn("centry_string out of memory\n"); + } + memcpy(ret,centry->data + centry->ofs, len); + ret[len] = 0; + centry->ofs += len; + return ret; +} + +/* pull a hash16 from a cache entry, using the supplied + talloc context +*/ +static char *centry_hash16(struct cache_entry *centry, TALLOC_CTX *mem_ctx) +{ + uint32_t len; + char *ret; + + len = centry_uint8(centry); + + if (len != 16) { + DBG_ERR("centry corruption? hash len (%u) != 16\n", + len ); + return NULL; + } + + if (!centry_check_bytes(centry, 16)) { + return NULL; + } + + ret = talloc_array(mem_ctx, char, 16); + if (!ret) { + smb_panic_fn("centry_hash out of memory\n"); + } + memcpy(ret,centry->data + centry->ofs, 16); + centry->ofs += 16; + return ret; +} + +/* pull a sid from a cache entry, using the supplied + talloc context +*/ +static bool centry_sid(struct cache_entry *centry, struct dom_sid *sid) +{ + char *sid_string; + bool ret; + + sid_string = centry_string(centry, talloc_tos()); + if (sid_string == NULL) { + return false; + } + ret = string_to_sid(sid, sid_string); + TALLOC_FREE(sid_string); + return ret; +} + + +/* + pull a NTSTATUS from a cache entry +*/ +static NTSTATUS centry_ntstatus(struct cache_entry *centry) +{ + NTSTATUS status; + + status = NT_STATUS(centry_uint32(centry)); + return status; +} + + +/* the server is considered down if it can't give us a sequence number */ +static bool wcache_server_down(struct winbindd_domain *domain) +{ + bool ret; + + if (!wcache->tdb) + return false; + + ret = (domain->sequence_number == DOM_SEQUENCE_NONE); + + if (ret) + DBG_DEBUG("wcache_server_down: server for Domain %s down\n", + domain->name ); + return ret; +} + +struct wcache_seqnum_state { + uint32_t *seqnum; + uint32_t *last_seq_check; +}; + +static int wcache_seqnum_parser(TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct wcache_seqnum_state *state = private_data; + + if (data.dsize != 8) { + DBG_DEBUG("wcache_fetch_seqnum: invalid data size %d\n", + (int)data.dsize); + return -1; + } + + *state->seqnum = IVAL(data.dptr, 0); + *state->last_seq_check = IVAL(data.dptr, 4); + return 0; +} + +static bool wcache_fetch_seqnum(const char *domain_name, uint32_t *seqnum, + uint32_t *last_seq_check) +{ + struct wcache_seqnum_state state = { + .seqnum = seqnum, .last_seq_check = last_seq_check + }; + size_t len = strlen(domain_name); + char keystr[len+8]; + TDB_DATA key = { .dptr = (uint8_t *)keystr, .dsize = sizeof(keystr) }; + int ret; + + if (wcache->tdb == NULL) { + DBG_DEBUG("wcache_fetch_seqnum: tdb == NULL\n"); + return false; + } + + snprintf(keystr, sizeof(keystr), "SEQNUM/%s", domain_name); + + ret = tdb_parse_record(wcache->tdb, key, wcache_seqnum_parser, + &state); + return (ret == 0); +} + +static NTSTATUS fetch_cache_seqnum( struct winbindd_domain *domain, time_t now ) +{ + uint32_t last_check, time_diff; + + if (!wcache_fetch_seqnum(domain->name, &domain->sequence_number, + &last_check)) { + return NT_STATUS_UNSUCCESSFUL; + } + domain->last_seq_check = last_check; + + /* have we expired? */ + + time_diff = now - domain->last_seq_check; + if ((int)time_diff > lp_winbind_cache_time()) { + DBG_DEBUG("fetch_cache_seqnum: timeout [%s][%u @ %u]\n", + domain->name, domain->sequence_number, + (uint32_t)domain->last_seq_check); + return NT_STATUS_UNSUCCESSFUL; + } + + DBG_DEBUG("fetch_cache_seqnum: success [%s][%u @ %u]\n", + domain->name, domain->sequence_number, + (uint32_t)domain->last_seq_check); + + return NT_STATUS_OK; +} + +bool wcache_store_seqnum(const char *domain_name, uint32_t seqnum, + time_t last_seq_check) +{ + size_t len = strlen(domain_name); + char keystr[len+8]; + TDB_DATA key = { .dptr = (uint8_t *)keystr, .dsize = sizeof(keystr) }; + uint8_t buf[8]; + int ret; + + if (wcache->tdb == NULL) { + DBG_DEBUG("wcache_store_seqnum: wcache->tdb == NULL\n"); + return false; + } + + snprintf(keystr, sizeof(keystr), "SEQNUM/%s", domain_name); + + SIVAL(buf, 0, seqnum); + SIVAL(buf, 4, last_seq_check); + + ret = tdb_store(wcache->tdb, key, make_tdb_data(buf, sizeof(buf)), + TDB_REPLACE); + if (ret != 0) { + DBG_DEBUG("tdb_store_bystring failed: %s\n", + tdb_errorstr(wcache->tdb)); + return false; + } + + DBG_DEBUG("wcache_store_seqnum: success [%s][%u @ %u]\n", + domain_name, seqnum, (unsigned)last_seq_check); + + return true; +} + +static bool store_cache_seqnum( struct winbindd_domain *domain ) +{ + return wcache_store_seqnum(domain->name, domain->sequence_number, + domain->last_seq_check); +} + +/* + refresh the domain sequence number on timeout. +*/ + +static void refresh_sequence_number(struct winbindd_domain *domain) +{ + NTSTATUS status; + unsigned time_diff; + time_t t = time(NULL); + unsigned cache_time = lp_winbind_cache_time(); + + if (is_domain_offline(domain)) { + return; + } + + get_cache( domain ); + + time_diff = t - domain->last_seq_check; + + /* see if we have to refetch the domain sequence number */ + if ((time_diff < cache_time) && + (domain->sequence_number != DOM_SEQUENCE_NONE) && + NT_STATUS_IS_OK(domain->last_status)) { + DBG_DEBUG("refresh_sequence_number: %s time ok\n", domain->name); + goto done; + } + + /* try to get the sequence number from the tdb cache first */ + /* this will update the timestamp as well */ + + status = fetch_cache_seqnum( domain, t ); + if (NT_STATUS_IS_OK(status) && + (domain->sequence_number != DOM_SEQUENCE_NONE) && + NT_STATUS_IS_OK(domain->last_status)) { + goto done; + } + + /* just use the current time */ + domain->last_status = NT_STATUS_OK; + domain->sequence_number = time(NULL); + domain->last_seq_check = time(NULL); + + /* save the new sequence number in the cache */ + store_cache_seqnum( domain ); + +done: + DBG_DEBUG("refresh_sequence_number: %s seq number is now %d\n", + domain->name, domain->sequence_number); + + return; +} + +/* + decide if a cache entry has expired +*/ +static bool centry_expired(struct winbindd_domain *domain, const char *keystr, struct cache_entry *centry) +{ + /* If we've been told to be offline - stay in that state... */ + if (lp_winbind_offline_logon() && get_global_winbindd_state_offline()) { + DBG_DEBUG("centry_expired: Key %s for domain %s valid as winbindd is globally offline.\n", + keystr, domain->name ); + return false; + } + + /* when the domain is offline return the cached entry. + * This deals with transient offline states... */ + + if (!domain->online) { + DBG_DEBUG("centry_expired: Key %s for domain %s valid as domain is offline.\n", + keystr, domain->name ); + return false; + } + + /* if the server is OK and our cache entry came from when it was down then + the entry is invalid */ + if ((domain->sequence_number != DOM_SEQUENCE_NONE) && + (centry->sequence_number == DOM_SEQUENCE_NONE)) { + DBG_DEBUG("centry_expired: Key %s for domain %s invalid sequence.\n", + keystr, domain->name ); + return true; + } + + /* if the server is down or the cache entry is not older than the + current sequence number or it did not timeout then it is OK */ + if (wcache_server_down(domain) + || ((centry->sequence_number == domain->sequence_number) + && ((time_t)centry->timeout > time(NULL)))) { + DBG_DEBUG("centry_expired: Key %s for domain %s is good.\n", + keystr, domain->name ); + return false; + } + + DBG_DEBUG("centry_expired: Key %s for domain %s expired\n", + keystr, domain->name ); + + /* it's expired */ + return true; +} + +static struct cache_entry *wcache_fetch_raw(char *kstr) +{ + TDB_DATA data; + struct cache_entry *centry; + TDB_DATA key; + + key = string_tdb_data(kstr); + data = tdb_fetch(wcache->tdb, key); + if (!data.dptr) { + /* a cache miss */ + return NULL; + } + + centry = SMB_XMALLOC_P(struct cache_entry); + centry->data = (unsigned char *)data.dptr; + centry->len = data.dsize; + centry->ofs = 0; + + if (centry->len < 16) { + /* huh? corrupt cache? */ + DBG_DEBUG("wcache_fetch_raw: Corrupt cache for key %s " + "(len < 16)?\n", kstr); + centry_free(centry); + return NULL; + } + + centry->status = centry_ntstatus(centry); + centry->sequence_number = centry_uint32(centry); + centry->timeout = centry_uint64_t(centry); + + return centry; +} + +static bool is_my_own_sam_domain(struct winbindd_domain *domain) +{ + if (strequal(domain->name, get_global_sam_name()) && + sid_check_is_our_sam(&domain->sid)) { + return true; + } + + return false; +} + +static bool is_builtin_domain(struct winbindd_domain *domain) +{ + if (strequal(domain->name, "BUILTIN") && + sid_check_is_builtin(&domain->sid)) { + return true; + } + + return false; +} + +/* + fetch an entry from the cache, with a varargs key. auto-fetch the sequence + number and return status +*/ +static struct cache_entry *wcache_fetch(struct winbind_cache *cache, + struct winbindd_domain *domain, + const char *format, ...) PRINTF_ATTRIBUTE(3,4); +static struct cache_entry *wcache_fetch(struct winbind_cache *cache, + struct winbindd_domain *domain, + const char *format, ...) +{ + va_list ap; + char *kstr; + struct cache_entry *centry; + int ret; + + if (!winbindd_use_cache() || + is_my_own_sam_domain(domain) || + is_builtin_domain(domain)) { + return NULL; + } + + refresh_sequence_number(domain); + + va_start(ap, format); + ret = vasprintf(&kstr, format, ap); + va_end(ap); + + if (ret == -1) { + return NULL; + } + + centry = wcache_fetch_raw(kstr); + if (centry == NULL) { + free(kstr); + return NULL; + } + + if (centry_expired(domain, kstr, centry)) { + + DBG_DEBUG("wcache_fetch: entry %s expired for domain %s\n", + kstr, domain->name ); + + centry_free(centry); + free(kstr); + return NULL; + } + + DBG_DEBUG("wcache_fetch: returning entry %s for domain %s\n", + kstr, domain->name ); + + free(kstr); + return centry; +} + +static void wcache_delete(const char *format, ...) PRINTF_ATTRIBUTE(1,2); +static void wcache_delete(const char *format, ...) +{ + va_list ap; + char *kstr; + TDB_DATA key; + int ret; + + va_start(ap, format); + ret = vasprintf(&kstr, format, ap); + va_end(ap); + + if (ret == -1) { + return; + } + + key = string_tdb_data(kstr); + + tdb_delete(wcache->tdb, key); + free(kstr); +} + +/* + make sure we have at least len bytes available in a centry +*/ +static void centry_expand(struct cache_entry *centry, uint32_t len) +{ + if (centry->len - centry->ofs >= len) + return; + centry->len *= 2; + centry->data = SMB_REALLOC_ARRAY(centry->data, unsigned char, + centry->len); + if (!centry->data) { + DBG_ERR("out of memory: needed %d bytes in centry_expand\n", centry->len); + smb_panic_fn("out of memory in centry_expand"); + } +} + +/* + push a uint64_t into a centry +*/ +static void centry_put_uint64_t(struct cache_entry *centry, uint64_t v) +{ + centry_expand(centry, 8); + SBVAL(centry->data, centry->ofs, v); + centry->ofs += 8; +} + +/* + push a uint32_t into a centry +*/ +static void centry_put_uint32(struct cache_entry *centry, uint32_t v) +{ + centry_expand(centry, 4); + SIVAL(centry->data, centry->ofs, v); + centry->ofs += 4; +} + +/* + push a uint16_t into a centry +*/ +static void centry_put_uint16(struct cache_entry *centry, uint16_t v) +{ + centry_expand(centry, 2); + SSVAL(centry->data, centry->ofs, v); + centry->ofs += 2; +} + +/* + push a uint8_t into a centry +*/ +static void centry_put_uint8(struct cache_entry *centry, uint8_t v) +{ + centry_expand(centry, 1); + SCVAL(centry->data, centry->ofs, v); + centry->ofs += 1; +} + +/* + push a string into a centry + */ +static void centry_put_string(struct cache_entry *centry, const char *s) +{ + int len; + + if (!s) { + /* null strings are marked as len 0xFFFF */ + centry_put_uint8(centry, 0xFF); + return; + } + + len = strlen(s); + /* can't handle more than 254 char strings. Truncating is probably best */ + if (len > 254) { + DBG_DEBUG("centry_put_string: truncating len (%d) to: 254\n", len); + len = 254; + } + centry_put_uint8(centry, len); + centry_expand(centry, len); + memcpy(centry->data + centry->ofs, s, len); + centry->ofs += len; +} + +/* + push a 16 byte hash into a centry - treat as 16 byte string. + */ +static void centry_put_hash16(struct cache_entry *centry, const uint8_t val[16]) +{ + centry_put_uint8(centry, 16); + centry_expand(centry, 16); + memcpy(centry->data + centry->ofs, val, 16); + centry->ofs += 16; +} + +static void centry_put_sid(struct cache_entry *centry, const struct dom_sid *sid) +{ + struct dom_sid_buf sid_string; + centry_put_string(centry, dom_sid_str_buf(sid, &sid_string)); +} + + +/* + put NTSTATUS into a centry +*/ +static void centry_put_ntstatus(struct cache_entry *centry, NTSTATUS status) +{ + uint32_t status_value = NT_STATUS_V(status); + centry_put_uint32(centry, status_value); +} + + +/* + push a NTTIME into a centry +*/ +static void centry_put_nttime(struct cache_entry *centry, NTTIME nt) +{ + centry_expand(centry, 8); + SIVAL(centry->data, centry->ofs, nt & 0xFFFFFFFF); + centry->ofs += 4; + SIVAL(centry->data, centry->ofs, nt >> 32); + centry->ofs += 4; +} + +/* + push a time_t into a centry - use a 64 bit size. + NTTIME here is being used as a convenient 64-bit size. +*/ +static void centry_put_time(struct cache_entry *centry, time_t t) +{ + NTTIME nt = (NTTIME)t; + centry_put_nttime(centry, nt); +} + +/* + start a centry for output. When finished, call centry_end() +*/ +static struct cache_entry *centry_start(struct winbindd_domain *domain, + NTSTATUS status) +{ + struct cache_entry *centry; + + if (!wcache->tdb) + return NULL; + + centry = SMB_XMALLOC_P(struct cache_entry); + + centry->len = 8192; /* reasonable default */ + centry->data = SMB_XMALLOC_ARRAY(uint8_t, centry->len); + centry->ofs = 0; + centry->sequence_number = domain->sequence_number; + centry->timeout = lp_winbind_cache_time() + time(NULL); + centry_put_ntstatus(centry, status); + centry_put_uint32(centry, centry->sequence_number); + centry_put_uint64_t(centry, centry->timeout); + return centry; +} + +/* + finish a centry and write it to the tdb +*/ +static void centry_end(struct cache_entry *centry, const char *format, ...) PRINTF_ATTRIBUTE(2,3); +static void centry_end(struct cache_entry *centry, const char *format, ...) +{ + va_list ap; + char *kstr; + TDB_DATA key, data; + int ret; + + if (!winbindd_use_cache()) { + return; + } + + va_start(ap, format); + ret = vasprintf(&kstr, format, ap); + va_end(ap); + + if (ret == -1) { + return; + } + + key = string_tdb_data(kstr); + data.dptr = centry->data; + data.dsize = centry->ofs; + + tdb_store(wcache->tdb, key, data, TDB_REPLACE); + free(kstr); +} + +static void wcache_save_name_to_sid(struct winbindd_domain *domain, + NTSTATUS status, const char *domain_name, + const char *name, const struct dom_sid *sid, + enum lsa_SidType type) +{ + bool ok; + + ok = namemap_cache_set_name2sid(domain_name, name, sid, type, + time(NULL) + lp_winbind_cache_time()); + if (!ok) { + DBG_DEBUG("namemap_cache_set_name2sid failed\n"); + } + + /* + * Don't store the reverse mapping. The name came from user + * input, and we might not have the correct capitalization, + * which is important for nsswitch. + */ +} + +static void wcache_save_sid_to_name(struct winbindd_domain *domain, NTSTATUS status, + const struct dom_sid *sid, const char *domain_name, const char *name, enum lsa_SidType type) +{ + bool ok; + + ok = namemap_cache_set_sid2name(sid, domain_name, name, type, + time(NULL) + lp_winbind_cache_time()); + if (!ok) { + DBG_DEBUG("namemap_cache_set_sid2name failed\n"); + } + + if (type != SID_NAME_UNKNOWN) { + ok = namemap_cache_set_name2sid( + domain_name, name, sid, type, + time(NULL) + lp_winbind_cache_time()); + if (!ok) { + DBG_DEBUG("namemap_cache_set_name2sid failed\n"); + } + } +} + +static void wcache_save_lockout_policy(struct winbindd_domain *domain, + NTSTATUS status, + struct samr_DomInfo12 *lockout_policy) +{ + struct cache_entry *centry; + + centry = centry_start(domain, status); + if (!centry) + return; + + centry_put_nttime(centry, lockout_policy->lockout_duration); + centry_put_nttime(centry, lockout_policy->lockout_window); + centry_put_uint16(centry, lockout_policy->lockout_threshold); + + centry_end(centry, "LOC_POL/%s", domain->name); + + DBG_DEBUG("wcache_save_lockout_policy: %s\n", domain->name); + + centry_free(centry); +} + + + +static void wcache_save_password_policy(struct winbindd_domain *domain, + NTSTATUS status, + struct samr_DomInfo1 *policy) +{ + struct cache_entry *centry; + + centry = centry_start(domain, status); + if (!centry) + return; + + centry_put_uint16(centry, policy->min_password_length); + centry_put_uint16(centry, policy->password_history_length); + centry_put_uint32(centry, policy->password_properties); + centry_put_nttime(centry, policy->max_password_age); + centry_put_nttime(centry, policy->min_password_age); + + centry_end(centry, "PWD_POL/%s", domain->name); + + DBG_DEBUG("wcache_save_password_policy: %s\n", domain->name); + + 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); + (void)strupper_m(uname); + centry_end(centry, "NSS/NA/%s", uname); + + DBG_DEBUG("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); + (void)strupper_m(uname); + centry_end(centry, "NSS/AN/%s", uname); + + DBG_DEBUG("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; + + upper_name = talloc_strdup_upper(mem_ctx, name); + if (upper_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + centry = wcache_fetch(cache, domain, "NSS/NA/%s", upper_name); + + talloc_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); + + DBG_DEBUG("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 (is_domain_offline(domain)) { + DBG_DEBUG("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)"); + } + + DBG_INFO("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; + + upper_name = talloc_strdup(mem_ctx, alias); + if (upper_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + if (!strupper_m(upper_name)) { + talloc_free(upper_name); + return NT_STATUS_INVALID_PARAMETER; + } + + centry = wcache_fetch(cache, domain, "NSS/AN/%s", upper_name); + + talloc_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); + + DBG_DEBUG("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 (is_domain_offline(domain)) { + DBG_DEBUG("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, '@')) { + DBG_DEBUG("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)"); + } + + DBG_INFO("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 struct dom_sid *sid) +{ + struct winbind_cache *cache = get_cache(domain); + int ret; + struct dom_sid_buf tmp; + fstring key_str; + uint32_t rid; + + if (!cache->tdb) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + if (is_null_sid(sid)) { + return NT_STATUS_INVALID_SID; + } + + if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) { + return NT_STATUS_INVALID_SID; + } + + fstr_sprintf(key_str, "CRED/%s", dom_sid_str_buf(sid, &tmp)); + + ret = tdb_exists(cache->tdb, string_tdb_data(key_str)); + if (ret != 1) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/* Lookup creds for a SID - copes with old (unsalted) creds as well + as new salted ones. */ + +NTSTATUS wcache_get_creds(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + const uint8_t **cached_nt_pass, + const uint8_t **cached_salt) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + uint32_t rid; + struct dom_sid_buf sidstr; + + if (!cache->tdb) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + if (is_null_sid(sid)) { + return NT_STATUS_INVALID_SID; + } + + if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) { + return NT_STATUS_INVALID_SID; + } + + /* Try and get a salted cred first. If we can't + fall back to an unsalted cred. */ + + centry = wcache_fetch(cache, domain, "CRED/%s", + dom_sid_str_buf(sid, &sidstr)); + if (!centry) { + DBG_DEBUG("wcache_get_creds: entry for [CRED/%s] not found\n", + dom_sid_str_buf(sid, &sidstr)); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* + * We don't use the time element at this moment, + * but we have to consume it, so that we don't + * need to change the disk format of the cache. + */ + (void)centry_time(centry); + + /* In the salted case this isn't actually the nt_hash itself, + but the MD5 of the salt + nt_hash. Let the caller + sort this out. It can tell as we only return the cached_salt + if we are returning a salted cred. */ + + *cached_nt_pass = (const uint8_t *)centry_hash16(centry, mem_ctx); + if (*cached_nt_pass == NULL) { + + dom_sid_str_buf(sid, &sidstr); + + /* Bad (old) cred cache. Delete and pretend we + don't have it. */ + DBG_WARNING("wcache_get_creds: bad entry for [CRED/%s] - deleting\n", + sidstr.buf); + wcache_delete("CRED/%s", sidstr.buf); + centry_free(centry); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* We only have 17 bytes more data in the salted cred case. */ + if (centry->len - centry->ofs == 17) { + *cached_salt = (const uint8_t *)centry_hash16(centry, mem_ctx); + } else { + *cached_salt = NULL; + } + + dump_data_pw("cached_nt_pass", *cached_nt_pass, NT_HASH_LEN); + if (*cached_salt) { + dump_data_pw("cached_salt", *cached_salt, NT_HASH_LEN); + } + + status = centry->status; + + DBG_DEBUG("wcache_get_creds: [Cached] - cached creds for user %s status: %s\n", + dom_sid_str_buf(sid, &sidstr), + nt_errstr(status) ); + + centry_free(centry); + return status; +} + +/* Store creds for a SID - only writes out new salted ones. */ + +NTSTATUS wcache_save_creds(struct winbindd_domain *domain, + const struct dom_sid *sid, + const uint8_t nt_pass[NT_HASH_LEN]) +{ + struct cache_entry *centry; + struct dom_sid_buf sid_str; + uint32_t rid; + uint8_t cred_salt[NT_HASH_LEN]; + uint8_t salted_hash[NT_HASH_LEN]; + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + if (is_null_sid(sid)) { + return NT_STATUS_INVALID_SID; + } + + if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) { + return NT_STATUS_INVALID_SID; + } + + centry = centry_start(domain, NT_STATUS_OK); + if (!centry) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + dump_data_pw("nt_pass", nt_pass, NT_HASH_LEN); + + centry_put_time(centry, time(NULL)); + + /* Create a salt and then salt the hash. */ + generate_random_buffer(cred_salt, NT_HASH_LEN); + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + centry_free(centry); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + + rc = gnutls_hash(hash_hnd, cred_salt, 16); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + centry_free(centry); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + rc = gnutls_hash(hash_hnd, nt_pass, 16); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + centry_free(centry); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + gnutls_hash_deinit(hash_hnd, salted_hash); + + centry_put_hash16(centry, salted_hash); + centry_put_hash16(centry, cred_salt); + centry_end(centry, "CRED/%s", dom_sid_str_buf(sid, &sid_str)); + + DBG_DEBUG("wcache_save_creds: %s\n", sid_str.buf); + + centry_free(centry); + + return NT_STATUS_OK; +} + + +/* Query display info. This is the basic user list fn */ +NTSTATUS wb_cache_query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **prids) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + uint32_t num_rids = 0; + uint32_t *rids = NULL; + NTSTATUS status; + unsigned int i, retry; + bool old_status = domain->online; + + *prids = NULL; + + if (!cache->tdb) + goto do_query; + + centry = wcache_fetch(cache, domain, "UL/%s", domain->name); + if (!centry) + goto do_query; + +do_fetch_cache: + num_rids = centry_uint32(centry); + + if (num_rids == 0) { + goto do_cached; + } + + rids = talloc_array(mem_ctx, uint32_t, num_rids); + if (rids == NULL) { + centry_free(centry); + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_rids; i++) { + rids[i] = centry_uint32(centry); + } + +do_cached: + status = centry->status; + + DBG_DEBUG("query_user_list: [Cached] - cached list for domain %s status: %s\n", + domain->name, nt_errstr(status) ); + + centry_free(centry); + return status; + +do_query: + + /* Put the query_user_list() in a retry loop. There appears to be + * some bug either with Windows 2000 or Samba's handling of large + * rpc replies. This manifests itself as sudden disconnection + * at a random point in the enumeration of a large (60k) user list. + * The retry loop simply tries the operation again. )-: It's not + * pretty but an acceptable workaround until we work out what the + * real problem is. */ + + retry = 0; + do { + + DBG_DEBUG("query_user_list: [Cached] - doing backend query for list for domain %s\n", + domain->name ); + + rids = NULL; + status = domain->backend->query_user_list(domain, mem_ctx, + &rids); + num_rids = talloc_array_length(rids); + + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("query_user_list: returned 0x%08x, " + "retrying\n", NT_STATUS_V(status)); + } + reset_cm_connection_on_error(domain, NULL, status); + if (NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) { + DBG_NOTICE("query_user_list: flushing " + "connection cache\n"); + invalidate_cm_connection(domain); + } + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + /* store partial response. */ + if (num_rids > 0) { + /* + * humm, what about the status used for cache? + * Should it be NT_STATUS_OK? + */ + break; + } + /* + * domain is offline now, and there is no user entries, + * try to fetch from cache again. + */ + if (cache->tdb && !domain->online && !domain->internal && old_status) { + centry = wcache_fetch(cache, domain, "UL/%s", domain->name); + /* partial response... */ + if (!centry) { + goto skip_save; + } else { + goto do_fetch_cache; + } + } else { + goto skip_save; + } + } + + } while (NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_UNSUCCESSFUL) && + (retry++ < 5)); + + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + centry = centry_start(domain, status); + if (!centry) + goto skip_save; + centry_put_uint32(centry, num_rids); + for (i=0; i<num_rids; i++) { + centry_put_uint32(centry, rids[i]); + } + centry_end(centry, "UL/%s", domain->name); + centry_free(centry); + + *prids = rids; + +skip_save: + return status; +} + +/* list all domain groups */ +NTSTATUS wb_cache_enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + bool old_status; + + old_status = domain->online; + if (!cache->tdb) + goto do_query; + + centry = wcache_fetch(cache, domain, "GL/%s/domain", domain->name); + if (!centry) + goto do_query; + +do_fetch_cache: + *num_entries = centry_uint32(centry); + + if (*num_entries == 0) + goto do_cached; + + (*info) = talloc_array(mem_ctx, struct wb_acct_info, *num_entries); + if (! (*info)) { + smb_panic_fn("enum_dom_groups out of memory"); + } + for (i=0; i<(*num_entries); i++) { + (*info)[i].acct_name = centry_string(centry, (*info)); + (*info)[i].acct_desc = centry_string(centry, (*info)); + (*info)[i].rid = centry_uint32(centry); + } + +do_cached: + status = centry->status; + + DBG_DEBUG("enum_dom_groups: [Cached] - cached list for domain %s status: %s\n", + domain->name, nt_errstr(status) ); + + centry_free(centry); + return status; + +do_query: + *num_entries = 0; + *info = NULL; + + DBG_DEBUG("enum_dom_groups: [Cached] - doing backend query for list for domain %s\n", + domain->name ); + + status = domain->backend->enum_dom_groups(domain, mem_ctx, num_entries, info); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (cache->tdb && + !domain->online && + !domain->internal && + old_status) { + centry = wcache_fetch(cache, domain, "GL/%s/domain", domain->name); + if (centry) { + goto do_fetch_cache; + } + } + } + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + centry = centry_start(domain, status); + if (!centry) + goto skip_save; + centry_put_uint32(centry, *num_entries); + for (i=0; i<(*num_entries); i++) { + centry_put_string(centry, (*info)[i].acct_name); + centry_put_string(centry, (*info)[i].acct_desc); + centry_put_uint32(centry, (*info)[i].rid); + } + centry_end(centry, "GL/%s/domain", domain->name); + centry_free(centry); + +skip_save: + return status; +} + +/* list all domain groups */ +NTSTATUS wb_cache_enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + bool old_status; + + old_status = domain->online; + if (!cache->tdb) + goto do_query; + + centry = wcache_fetch(cache, domain, "GL/%s/local", domain->name); + if (!centry) + goto do_query; + +do_fetch_cache: + *num_entries = centry_uint32(centry); + + if (*num_entries == 0) + goto do_cached; + + (*info) = talloc_array(mem_ctx, struct wb_acct_info, *num_entries); + if (! (*info)) { + smb_panic_fn("enum_dom_groups out of memory"); + } + for (i=0; i<(*num_entries); i++) { + (*info)[i].acct_name = centry_string(centry, (*info)); + (*info)[i].acct_desc = centry_string(centry, (*info)); + (*info)[i].rid = centry_uint32(centry); + } + +do_cached: + + /* If we are returning cached data and the domain controller + is down then we don't know whether the data is up to date + or not. Return NT_STATUS_MORE_PROCESSING_REQUIRED to + indicate this. */ + + if (wcache_server_down(domain)) { + DBG_DEBUG("enum_local_groups: returning cached user list and server was down\n"); + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } else + status = centry->status; + + DBG_DEBUG("enum_local_groups: [Cached] - cached list for domain %s status: %s\n", + domain->name, nt_errstr(status) ); + + centry_free(centry); + return status; + +do_query: + *num_entries = 0; + *info = NULL; + + DBG_DEBUG("enum_local_groups: [Cached] - doing backend query for list for domain %s\n", + domain->name ); + + status = domain->backend->enum_local_groups(domain, mem_ctx, num_entries, info); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (cache->tdb && + !domain->internal && + !domain->online && + old_status) { + centry = wcache_fetch(cache, domain, "GL/%s/local", domain->name); + if (centry) { + goto do_fetch_cache; + } + } + } + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + centry = centry_start(domain, status); + if (!centry) + goto skip_save; + centry_put_uint32(centry, *num_entries); + for (i=0; i<(*num_entries); i++) { + centry_put_string(centry, (*info)[i].acct_name); + centry_put_string(centry, (*info)[i].acct_desc); + centry_put_uint32(centry, (*info)[i].rid); + } + centry_end(centry, "GL/%s/local", domain->name); + centry_free(centry); + +skip_save: + return status; +} + +struct wcache_name_to_sid_state { + struct dom_sid *sid; + enum lsa_SidType *type; + bool offline; + bool found; +}; + +static void wcache_name_to_sid_fn(const struct dom_sid *sid, + enum lsa_SidType type, + bool expired, + void *private_data) +{ + struct wcache_name_to_sid_state *state = private_data; + + *state->sid = *sid; + *state->type = type; + state->found = (!expired || state->offline); +} + +static NTSTATUS wcache_name_to_sid(struct winbindd_domain *domain, + const char *domain_name, + const char *name, + struct dom_sid *sid, + enum lsa_SidType *type) +{ + struct wcache_name_to_sid_state state = { + .sid = sid, .type = type, .found = false, + .offline = is_domain_offline(domain), + }; + bool ok; + + ok = namemap_cache_find_name(domain_name, name, wcache_name_to_sid_fn, + &state); + if (!ok) { + DBG_DEBUG("namemap_cache_find_name failed\n"); + return NT_STATUS_NOT_FOUND; + } + if (!state.found) { + DBG_DEBUG("cache entry not found\n"); + return NT_STATUS_NOT_FOUND; + } + if (*type == SID_NAME_UNKNOWN) { + return NT_STATUS_NONE_MAPPED; + } + + return NT_STATUS_OK; +} + +/* convert a single name to a sid in a domain */ +NTSTATUS wb_cache_name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + uint32_t flags, + struct dom_sid *sid, + enum lsa_SidType *type) +{ + NTSTATUS status; + bool old_status; + const char *dom_name; + + old_status = domain->online; + + status = wcache_name_to_sid(domain, domain_name, name, sid, type); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return status; + } + + ZERO_STRUCTP(sid); + + DBG_DEBUG("name_to_sid: [Cached] - doing backend query for name for domain %s\n", + domain->name ); + + winbindd_domain_init_backend(domain); + status = domain->backend->name_to_sid(domain, mem_ctx, domain_name, + name, flags, &dom_name, sid, type); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (!domain->internal && + !domain->online && + old_status) { + NTSTATUS cache_status; + cache_status = wcache_name_to_sid(domain, domain_name, name, sid, type); + return cache_status; + } + } + /* and save it */ + + if (domain->online && + (NT_STATUS_IS_OK(status) || NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))) { + enum lsa_SidType save_type = *type; + + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + save_type = SID_NAME_UNKNOWN; + } + + wcache_save_name_to_sid(domain, status, domain_name, name, sid, + save_type); + + /* Only save the reverse mapping if this was not a UPN */ + if (!strchr(name, '@')) { + if (!strupper_m(discard_const_p(char, domain_name))) { + return NT_STATUS_INVALID_PARAMETER; + } + (void)strlower_m(discard_const_p(char, name)); + wcache_save_sid_to_name(domain, status, sid, + dom_name, name, save_type); + } + } + + return status; +} + +struct wcache_sid_to_name_state { + TALLOC_CTX *mem_ctx; + char **domain_name; + char **name; + enum lsa_SidType *type; + bool offline; + bool found; +}; + +static void wcache_sid_to_name_fn(const char *domain, + const char *name, + enum lsa_SidType type, + bool expired, + void *private_data) +{ + struct wcache_sid_to_name_state *state = private_data; + + *state->domain_name = talloc_strdup(state->mem_ctx, domain); + if (*state->domain_name == NULL) { + return; + } + *state->name = talloc_strdup(state->mem_ctx, name); + if (*state->name == NULL) { + return; + } + *state->type = type; + state->found = (!expired || state->offline); +} + +static NTSTATUS wcache_sid_to_name(struct winbindd_domain *domain, + const struct dom_sid *sid, + TALLOC_CTX *mem_ctx, + char **domain_name, + char **name, + enum lsa_SidType *type) +{ + struct wcache_sid_to_name_state state = { + .mem_ctx = mem_ctx, .found = false, + .domain_name = domain_name, .name = name, .type = type, + .offline = is_domain_offline(domain) + }; + bool ok; + + ok = namemap_cache_find_sid(sid, wcache_sid_to_name_fn, &state); + if (!ok) { + DBG_DEBUG("namemap_cache_find_name failed\n"); + return NT_STATUS_NOT_FOUND; + } + if (!state.found) { + DBG_DEBUG("cache entry not found\n"); + return NT_STATUS_NOT_FOUND; + } + if (*type == SID_NAME_UNKNOWN) { + return NT_STATUS_NONE_MAPPED; + } + + return NT_STATUS_OK; +} + +/* convert a sid to a user or group name. The sid is guaranteed to be in the domain + given */ +NTSTATUS wb_cache_sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + char **domain_name, + char **name, + enum lsa_SidType *type) +{ + NTSTATUS status; + bool old_status; + + old_status = domain->online; + status = wcache_sid_to_name(domain, sid, mem_ctx, domain_name, name, + type); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return status; + } + + *name = NULL; + *domain_name = NULL; + + DBG_DEBUG("sid_to_name: [Cached] - doing backend query for name for domain %s\n", + domain->name ); + + winbindd_domain_init_backend(domain); + + status = domain->backend->sid_to_name(domain, mem_ctx, sid, domain_name, name, type); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (!domain->internal && + !domain->online && + old_status) { + NTSTATUS cache_status; + cache_status = wcache_sid_to_name(domain, sid, mem_ctx, + domain_name, name, type); + return cache_status; + } + } + /* and save it */ + if (!NT_STATUS_IS_OK(status)) { + return status; + } + wcache_save_sid_to_name(domain, status, sid, *domain_name, *name, *type); + + /* We can't save the name to sid mapping here, as with sid history a + * later name2sid would give the wrong sid. */ + + return status; +} + +NTSTATUS wb_cache_rids_to_names(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *domain_sid, + uint32_t *rids, + size_t num_rids, + char **domain_name, + char ***names, + enum lsa_SidType **types) +{ + struct winbind_cache *cache = get_cache(domain); + size_t i; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + bool have_mapped; + bool have_unmapped; + bool old_status; + + old_status = domain->online; + *domain_name = NULL; + *names = NULL; + *types = NULL; + + if (!cache->tdb) { + goto do_query; + } + + if (num_rids == 0) { + return NT_STATUS_OK; + } + + *names = talloc_array(mem_ctx, char *, num_rids); + *types = talloc_array(mem_ctx, enum lsa_SidType, num_rids); + + if ((*names == NULL) || (*types == NULL)) { + result = NT_STATUS_NO_MEMORY; + goto error; + } + + have_mapped = have_unmapped = false; + + for (i=0; i<num_rids; i++) { + struct dom_sid sid; + NTSTATUS status; + enum lsa_SidType type; + char *dom, *name; + + if (!sid_compose(&sid, domain_sid, rids[i])) { + result = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + status = wcache_sid_to_name(domain, &sid, *names, &dom, + &name, &type); + + (*types)[i] = SID_NAME_UNKNOWN; + (*names)[i] = talloc_strdup(*names, ""); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + /* not cached */ + goto do_query; + } + + if (NT_STATUS_IS_OK(status)) { + have_mapped = true; + (*types)[i] = type; + + if (*domain_name == NULL) { + *domain_name = dom; + } else { + TALLOC_FREE(dom); + } + + (*names)[i] = name; + + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + have_unmapped = true; + } else { + /* something's definitely wrong */ + result = status; + goto error; + } + } + + if (!have_mapped) { + return NT_STATUS_NONE_MAPPED; + } + if (!have_unmapped) { + return NT_STATUS_OK; + } + return STATUS_SOME_UNMAPPED; + + do_query: + + TALLOC_FREE(*names); + TALLOC_FREE(*types); + + result = domain->backend->rids_to_names(domain, mem_ctx, domain_sid, + rids, num_rids, domain_name, + names, types); + + if (NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (cache->tdb && + !domain->internal && + !domain->online && + old_status) { + have_mapped = have_unmapped = false; + + *names = talloc_array(mem_ctx, char *, num_rids); + if (*names == NULL) { + result = NT_STATUS_NO_MEMORY; + goto error; + } + + *types = talloc_array(mem_ctx, enum lsa_SidType, + num_rids); + if (*types == NULL) { + result = NT_STATUS_NO_MEMORY; + goto error; + } + + for (i=0; i<num_rids; i++) { + struct dom_sid sid; + NTSTATUS status; + enum lsa_SidType type; + char *dom, *name; + + if (!sid_compose(&sid, domain_sid, rids[i])) { + result = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + status = wcache_sid_to_name(domain, &sid, + *names, &dom, + &name, &type); + + (*types)[i] = SID_NAME_UNKNOWN; + (*names)[i] = talloc_strdup(*names, ""); + + if (NT_STATUS_IS_OK(status)) { + have_mapped = true; + (*types)[i] = type; + + if (*domain_name == NULL) { + *domain_name = dom; + } else { + TALLOC_FREE(dom); + } + + (*names)[i] = name; + + } else if (NT_STATUS_EQUAL( + status, + NT_STATUS_NONE_MAPPED)) { + have_unmapped = true; + } else { + /* something's definitely wrong */ + result = status; + goto error; + } + } + + if (!have_mapped) { + return NT_STATUS_NONE_MAPPED; + } + if (!have_unmapped) { + return NT_STATUS_OK; + } + return STATUS_SOME_UNMAPPED; + } + } + /* + None of the queried rids has been found so save all negative entries + */ + if (NT_STATUS_EQUAL(result, NT_STATUS_NONE_MAPPED)) { + for (i = 0; i < num_rids; i++) { + struct dom_sid sid; + const char *name = ""; + const enum lsa_SidType type = SID_NAME_UNKNOWN; + NTSTATUS status = NT_STATUS_NONE_MAPPED; + + if (!sid_compose(&sid, domain_sid, rids[i])) { + return NT_STATUS_INTERNAL_ERROR; + } + + wcache_save_sid_to_name(domain, status, &sid, *domain_name, + name, type); + } + + return result; + } + + /* + Some or all of the queried rids have been found. + */ + if (!NT_STATUS_IS_OK(result) && + !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) { + return result; + } + + refresh_sequence_number(domain); + + for (i=0; i<num_rids; i++) { + struct dom_sid sid; + NTSTATUS status; + + if (!sid_compose(&sid, domain_sid, rids[i])) { + result = NT_STATUS_INTERNAL_ERROR; + goto error; + } + + status = (*types)[i] == SID_NAME_UNKNOWN ? + NT_STATUS_NONE_MAPPED : NT_STATUS_OK; + + wcache_save_sid_to_name(domain, status, &sid, *domain_name, + (*names)[i], (*types)[i]); + } + + return result; + + error: + TALLOC_FREE(*names); + TALLOC_FREE(*types); + return result; +} + +static NTSTATUS wcache_query_user(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + struct wbint_userinfo *info) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + struct dom_sid_buf sid_string; + + if (cache->tdb == NULL) { + return NT_STATUS_NOT_FOUND; + } + + centry = wcache_fetch( + cache, domain, "U/%s", dom_sid_str_buf(user_sid, &sid_string)); + if (centry == NULL) { + return NT_STATUS_NOT_FOUND; + } + + /* if status is not ok then this is a negative hit + and the rest of the data doesn't matter */ + status = centry->status; + if (NT_STATUS_IS_OK(status)) { + info->domain_name = centry_string(centry, mem_ctx); + info->acct_name = centry_string(centry, mem_ctx); + info->full_name = centry_string(centry, mem_ctx); + info->homedir = centry_string(centry, mem_ctx); + info->shell = centry_string(centry, mem_ctx); + info->uid = centry_uint32(centry); + info->primary_gid = centry_uint32(centry); + info->primary_group_name = centry_string(centry, mem_ctx); + centry_sid(centry, &info->user_sid); + centry_sid(centry, &info->group_sid); + } + + DBG_DEBUG("query_user: [Cached] - cached info for domain %s status: " + "%s\n", domain->name, nt_errstr(status) ); + + centry_free(centry); + return status; +} + + +/** +* @brief Query a fullname from the username cache (for further gecos processing) +* +* @param domain A pointer to the winbindd_domain struct. +* @param mem_ctx The talloc context. +* @param user_sid The user sid. +* @param full_name A pointer to the full_name string. +* +* @return NTSTATUS code +*/ +NTSTATUS wcache_query_user_fullname(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + const char **full_name) +{ + NTSTATUS status; + struct wbint_userinfo info; + + status = wcache_query_user(domain, mem_ctx, user_sid, &info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (info.full_name != NULL) { + *full_name = talloc_strdup(mem_ctx, info.full_name); + if (*full_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS wcache_lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *pnum_sids, + struct dom_sid **psids) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + uint32_t i, num_sids; + struct dom_sid *sids; + struct dom_sid_buf sid_string; + + if (cache->tdb == NULL) { + return NT_STATUS_NOT_FOUND; + } + + centry = wcache_fetch( + cache, + domain, + "UG/%s", + dom_sid_str_buf(user_sid, &sid_string)); + if (centry == NULL) { + return NT_STATUS_NOT_FOUND; + } + + num_sids = centry_uint32(centry); + sids = talloc_array(mem_ctx, struct dom_sid, num_sids); + if (sids == NULL) { + centry_free(centry); + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_sids; i++) { + centry_sid(centry, &sids[i]); + } + + status = centry->status; + + DBG_DEBUG("lookup_usergroups: [Cached] - cached info for domain %s " + "status: %s\n", domain->name, nt_errstr(status)); + + centry_free(centry); + + *pnum_sids = num_sids; + *psids = sids; + return status; +} + +/* Lookup groups a user is a member of. */ +NTSTATUS wb_cache_lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *num_groups, + struct dom_sid **user_gids) +{ + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + struct dom_sid_buf sid_string; + bool old_status; + + old_status = domain->online; + status = wcache_lookup_usergroups(domain, mem_ctx, user_sid, + num_groups, user_gids); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return status; + } + + (*num_groups) = 0; + (*user_gids) = NULL; + + DBG_DEBUG("lookup_usergroups: [Cached] - doing backend query for info for domain %s\n", + domain->name ); + + status = domain->backend->lookup_usergroups(domain, mem_ctx, user_sid, num_groups, user_gids); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (!domain->internal && + !domain->online && + old_status) { + NTSTATUS cache_status; + cache_status = wcache_lookup_usergroups(domain, mem_ctx, user_sid, + num_groups, user_gids); + return cache_status; + } + } + if ( NT_STATUS_EQUAL(status, NT_STATUS_SYNCHRONIZATION_REQUIRED) ) + goto skip_save; + + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + centry = centry_start(domain, status); + if (!centry) + goto skip_save; + + centry_put_uint32(centry, *num_groups); + for (i=0; i<(*num_groups); i++) { + centry_put_sid(centry, &(*user_gids)[i]); + } + + centry_end(centry, "UG/%s", dom_sid_str_buf(user_sid, &sid_string)); + centry_free(centry); + +skip_save: + return status; +} + +static char *wcache_make_sidlist(TALLOC_CTX *mem_ctx, uint32_t num_sids, + const struct dom_sid *sids) +{ + uint32_t i; + char *sidlist; + + sidlist = talloc_strdup(mem_ctx, ""); + if (sidlist == NULL) { + return NULL; + } + for (i=0; i<num_sids; i++) { + struct dom_sid_buf tmp; + sidlist = talloc_asprintf_append_buffer( + sidlist, + "/%s", + dom_sid_str_buf(&sids[i], &tmp)); + if (sidlist == NULL) { + return NULL; + } + } + return sidlist; +} + +static NTSTATUS wcache_lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *pnum_aliases, + uint32_t **paliases) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + uint32_t i, num_aliases; + uint32_t *aliases; + NTSTATUS status; + char *sidlist; + + if (cache->tdb == NULL) { + return NT_STATUS_NOT_FOUND; + } + + if (num_sids == 0) { + *pnum_aliases = 0; + *paliases = NULL; + return NT_STATUS_OK; + } + + /* We need to cache indexed by the whole list of SIDs, the aliases + * resulting might come from any of the SIDs. */ + + sidlist = wcache_make_sidlist(talloc_tos(), num_sids, sids); + if (sidlist == NULL) { + return NT_STATUS_NO_MEMORY; + } + + centry = wcache_fetch(cache, domain, "UA%s", sidlist); + TALLOC_FREE(sidlist); + if (centry == NULL) { + return NT_STATUS_NOT_FOUND; + } + + num_aliases = centry_uint32(centry); + aliases = talloc_array(mem_ctx, uint32_t, num_aliases); + if (aliases == NULL) { + centry_free(centry); + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_aliases; i++) { + aliases[i] = centry_uint32(centry); + } + + status = centry->status; + + DBG_DEBUG("lookup_useraliases: [Cached] - cached info for domain: %s " + "status %s\n", domain->name, nt_errstr(status)); + + centry_free(centry); + + *pnum_aliases = num_aliases; + *paliases = aliases; + + return status; +} + +NTSTATUS wb_cache_lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *num_aliases, + uint32_t **alias_rids) +{ + struct cache_entry *centry = NULL; + NTSTATUS status; + char *sidlist; + uint32_t i; + bool old_status; + + old_status = domain->online; + status = wcache_lookup_useraliases(domain, mem_ctx, num_sids, sids, + num_aliases, alias_rids); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return status; + } + + (*num_aliases) = 0; + (*alias_rids) = NULL; + + DBG_DEBUG("lookup_usergroups: [Cached] - doing backend query for info " + "for domain %s\n", domain->name ); + + sidlist = wcache_make_sidlist(talloc_tos(), num_sids, sids); + if (sidlist == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = domain->backend->lookup_useraliases(domain, mem_ctx, + num_sids, sids, + num_aliases, alias_rids); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (!domain->internal && + !domain->online && + old_status) { + NTSTATUS cache_status; + cache_status = wcache_lookup_useraliases(domain, mem_ctx, num_sids, + sids, num_aliases, alias_rids); + return cache_status; + } + } + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + centry = centry_start(domain, status); + if (!centry) + goto skip_save; + centry_put_uint32(centry, *num_aliases); + for (i=0; i<(*num_aliases); i++) + centry_put_uint32(centry, (*alias_rids)[i]); + centry_end(centry, "UA%s", sidlist); + centry_free(centry); + + skip_save: + return status; +} + +static NTSTATUS wcache_lookup_aliasmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + uint32_t *num_names, + struct dom_sid **sid_mem) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + struct dom_sid_buf sid_string; + + if (cache->tdb == NULL) { + return NT_STATUS_NOT_FOUND; + } + + centry = wcache_fetch(cache, + domain, + "AM/%s", + dom_sid_str_buf(group_sid, &sid_string)); + if (centry == NULL) { + return NT_STATUS_NOT_FOUND; + } + + *sid_mem = NULL; + + *num_names = centry_uint32(centry); + if (*num_names == 0) { + centry_free(centry); + return NT_STATUS_OK; + } + + *sid_mem = talloc_array(mem_ctx, struct dom_sid, *num_names); + if (*sid_mem == NULL) { + TALLOC_FREE(*sid_mem); + centry_free(centry); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < (*num_names); i++) { + centry_sid(centry, &(*sid_mem)[i]); + } + + status = centry->status; + + D_DEBUG("[Cached] - cached info for domain %s " + "status: %s\n", + domain->name, + nt_errstr(status)); + + centry_free(centry); + return status; +} + +NTSTATUS wb_cache_lookup_aliasmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_sids, + struct dom_sid **sid_mem) +{ + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + struct dom_sid_buf sid_string; + bool old_status; + + old_status = domain->online; + status = wcache_lookup_aliasmem(domain, + mem_ctx, + group_sid, + num_sids, + sid_mem); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return status; + } + + (*num_sids) = 0; + (*sid_mem) = NULL; + + D_DEBUG("[Cached] - doing backend query for info for domain %s\n", + domain->name); + + status = domain->backend->lookup_aliasmem(domain, + mem_ctx, + group_sid, + type, + num_sids, + sid_mem); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (!domain->internal && !domain->online && old_status) { + NTSTATUS cache_status; + cache_status = wcache_lookup_aliasmem(domain, + mem_ctx, + group_sid, + num_sids, + sid_mem); + return cache_status; + } + } + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + centry = centry_start(domain, status); + if (!centry) + goto skip_save; + centry_put_uint32(centry, *num_sids); + for (i = 0; i < (*num_sids); i++) { + centry_put_sid(centry, &(*sid_mem)[i]); + } + centry_end(centry, "AM/%s", dom_sid_str_buf(group_sid, &sid_string)); + centry_free(centry); + +skip_save: + return status; +} + +static NTSTATUS wcache_lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + uint32_t *num_names, + struct dom_sid **sid_mem, char ***names, + uint32_t **name_types) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + struct dom_sid_buf sid_string; + + if (cache->tdb == NULL) { + return NT_STATUS_NOT_FOUND; + } + + centry = wcache_fetch( + cache, + domain, + "GM/%s", + dom_sid_str_buf(group_sid, &sid_string)); + if (centry == NULL) { + return NT_STATUS_NOT_FOUND; + } + + *sid_mem = NULL; + *names = NULL; + *name_types = NULL; + + *num_names = centry_uint32(centry); + if (*num_names == 0) { + centry_free(centry); + return NT_STATUS_OK; + } + + *sid_mem = talloc_array(mem_ctx, struct dom_sid, *num_names); + *names = talloc_array(mem_ctx, char *, *num_names); + *name_types = talloc_array(mem_ctx, uint32_t, *num_names); + + if ((*sid_mem == NULL) || (*names == NULL) || (*name_types == NULL)) { + TALLOC_FREE(*sid_mem); + TALLOC_FREE(*names); + TALLOC_FREE(*name_types); + centry_free(centry); + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<(*num_names); i++) { + centry_sid(centry, &(*sid_mem)[i]); + (*names)[i] = centry_string(centry, mem_ctx); + (*name_types)[i] = centry_uint32(centry); + } + + status = centry->status; + + DBG_DEBUG("lookup_groupmem: [Cached] - cached info for domain %s " + "status: %s\n", domain->name, nt_errstr(status)); + + centry_free(centry); + return status; +} + +NTSTATUS wb_cache_lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem, + char ***names, + uint32_t **name_types) +{ + struct cache_entry *centry = NULL; + NTSTATUS status; + unsigned int i; + struct dom_sid_buf sid_string; + bool old_status; + + old_status = domain->online; + status = wcache_lookup_groupmem(domain, mem_ctx, group_sid, num_names, + sid_mem, names, name_types); + if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + return status; + } + + (*num_names) = 0; + (*sid_mem) = NULL; + (*names) = NULL; + (*name_types) = NULL; + + DBG_DEBUG("lookup_groupmem: [Cached] - doing backend query for info for domain %s\n", + domain->name ); + + status = domain->backend->lookup_groupmem(domain, mem_ctx, group_sid, + type, num_names, + sid_mem, names, name_types); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (!domain->internal && + !domain->online && + old_status) { + NTSTATUS cache_status; + cache_status = wcache_lookup_groupmem(domain, mem_ctx, group_sid, + num_names, sid_mem, names, + name_types); + return cache_status; + } + } + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + centry = centry_start(domain, status); + if (!centry) + goto skip_save; + centry_put_uint32(centry, *num_names); + for (i=0; i<(*num_names); i++) { + centry_put_sid(centry, &(*sid_mem)[i]); + centry_put_string(centry, (*names)[i]); + centry_put_uint32(centry, (*name_types)[i]); + } + centry_end(centry, + "GM/%s", + dom_sid_str_buf(group_sid, &sid_string)); + centry_free(centry); + +skip_save: + return status; +} + +/* find the sequence number for a domain */ +NTSTATUS wb_cache_sequence_number(struct winbindd_domain *domain, + uint32_t *seq) +{ + refresh_sequence_number(domain); + + *seq = domain->sequence_number; + + return NT_STATUS_OK; +} + +/* enumerate trusted domains + * (we need to have the list of trustdoms in the cache when we go offline) - + * Guenther */ +NTSTATUS wb_cache_trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *trusts) +{ + NTSTATUS status; + struct winbind_cache *cache; + struct winbindd_tdc_domain *dom_list = NULL; + size_t num_domains = 0; + bool retval = false; + size_t i; + bool old_status; + + old_status = domain->online; + trusts->count = 0; + trusts->array = NULL; + + cache = get_cache(domain); + if (!cache || !cache->tdb) { + goto do_query; + } + + if (domain->online) { + goto do_query; + } + + retval = wcache_tdc_fetch_list(&dom_list, &num_domains); + if (!retval || !num_domains || !dom_list) { + TALLOC_FREE(dom_list); + goto do_query; + } + +do_fetch_cache: + trusts->array = talloc_zero_array(mem_ctx, struct netr_DomainTrust, num_domains); + if (!trusts->array) { + TALLOC_FREE(dom_list); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < num_domains; i++) { + struct netr_DomainTrust *trust; + struct dom_sid *sid; + struct winbindd_domain *dom; + + dom = find_domain_from_name_noinit(dom_list[i].domain_name); + if (dom && dom->internal) { + continue; + } + + trust = &trusts->array[trusts->count]; + trust->netbios_name = talloc_strdup(trusts->array, dom_list[i].domain_name); + trust->dns_name = talloc_strdup(trusts->array, dom_list[i].dns_name); + sid = talloc(trusts->array, struct dom_sid); + if (!trust->netbios_name || !trust->dns_name || + !sid) { + TALLOC_FREE(dom_list); + TALLOC_FREE(trusts->array); + return NT_STATUS_NO_MEMORY; + } + + trust->trust_flags = dom_list[i].trust_flags; + trust->trust_attributes = dom_list[i].trust_attribs; + trust->trust_type = dom_list[i].trust_type; + sid_copy(sid, &dom_list[i].sid); + trust->sid = sid; + trusts->count++; + } + + TALLOC_FREE(dom_list); + return NT_STATUS_OK; + +do_query: + DBG_DEBUG("trusted_domains: [Cached] - doing backend query for info for domain %s\n", + domain->name ); + + status = domain->backend->trusted_domains(domain, mem_ctx, trusts); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (!domain->internal && + !domain->online && + old_status) { + retval = wcache_tdc_fetch_list(&dom_list, &num_domains); + if (retval && num_domains && dom_list) { + TALLOC_FREE(trusts->array); + trusts->count = 0; + goto do_fetch_cache; + } + } + } + /* no trusts gives NT_STATUS_NO_MORE_ENTRIES resetting to NT_STATUS_OK + * so that the generic centry handling still applies correctly - + * Guenther*/ + + if (!NT_STATUS_IS_ERR(status)) { + status = NT_STATUS_OK; + } + return status; +} + +/* get lockout policy */ +NTSTATUS wb_cache_lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *policy) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + bool old_status; + + old_status = domain->online; + if (!cache->tdb) + goto do_query; + + centry = wcache_fetch(cache, domain, "LOC_POL/%s", domain->name); + + if (!centry) + goto do_query; + +do_fetch_cache: + policy->lockout_duration = centry_nttime(centry); + policy->lockout_window = centry_nttime(centry); + policy->lockout_threshold = centry_uint16(centry); + + status = centry->status; + + DBG_DEBUG("lockout_policy: [Cached] - cached info for domain %s status: %s\n", + domain->name, nt_errstr(status) ); + + centry_free(centry); + return status; + +do_query: + ZERO_STRUCTP(policy); + + DBG_DEBUG("lockout_policy: [Cached] - doing backend query for info for domain %s\n", + domain->name ); + + status = domain->backend->lockout_policy(domain, mem_ctx, policy); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (cache->tdb && + !domain->internal && + !domain->online && + old_status) { + centry = wcache_fetch(cache, domain, "LOC_POL/%s", domain->name); + if (centry) { + goto do_fetch_cache; + } + } + } + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + wcache_save_lockout_policy(domain, status, policy); + + return status; +} + +/* get password policy */ +NTSTATUS wb_cache_password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *policy) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + bool old_status; + + old_status = domain->online; + if (!cache->tdb) + goto do_query; + + centry = wcache_fetch(cache, domain, "PWD_POL/%s", domain->name); + + if (!centry) + goto do_query; + +do_fetch_cache: + policy->min_password_length = centry_uint16(centry); + policy->password_history_length = centry_uint16(centry); + policy->password_properties = centry_uint32(centry); + policy->max_password_age = centry_nttime(centry); + policy->min_password_age = centry_nttime(centry); + + status = centry->status; + + DBG_DEBUG("lockout_policy: [Cached] - cached info for domain %s status: %s\n", + domain->name, nt_errstr(status) ); + + centry_free(centry); + return status; + +do_query: + ZERO_STRUCTP(policy); + + DBG_DEBUG("password_policy: [Cached] - doing backend query for info for domain %s\n", + domain->name ); + + status = domain->backend->password_policy(domain, mem_ctx, policy); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + if (!domain->internal && old_status) { + set_domain_offline(domain); + } + if (cache->tdb && + !domain->internal && + !domain->online && + old_status) { + centry = wcache_fetch(cache, domain, "PWD_POL/%s", domain->name); + if (centry) { + goto do_fetch_cache; + } + } + } + /* and save it */ + refresh_sequence_number(domain); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + wcache_save_password_policy(domain, status, policy); + + return status; +} + + +/* Invalidate cached user and group lists coherently */ + +static int traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, + void *state) +{ + if (strncmp((const char *)kbuf.dptr, "UL/", 3) == 0 || + strncmp((const char *)kbuf.dptr, "GL/", 3) == 0) + tdb_delete(the_tdb, kbuf); + + return 0; +} + +/* Invalidate the getpwnam and getgroups entries for a winbindd domain */ + +void wcache_invalidate_samlogon(struct winbindd_domain *domain, + const struct dom_sid *sid) +{ + fstring key_str; + struct dom_sid_buf sid_string; + struct winbind_cache *cache; + + /* don't clear cached U/SID and UG/SID entries when we want to logon + * offline - gd */ + + if (lp_winbind_offline_logon()) { + return; + } + + if (!domain) + return; + + cache = get_cache(domain); + + if (!cache->tdb) { + return; + } + + /* Clear U/SID cache entry */ + fstr_sprintf(key_str, "U/%s", dom_sid_str_buf(sid, &sid_string)); + DBG_DEBUG("wcache_invalidate_samlogon: clearing %s\n", key_str); + tdb_delete(cache->tdb, string_tdb_data(key_str)); + + /* Clear UG/SID cache entry */ + fstr_sprintf(key_str, "UG/%s", dom_sid_str_buf(sid, &sid_string)); + DBG_DEBUG("wcache_invalidate_samlogon: clearing %s\n", key_str); + tdb_delete(cache->tdb, string_tdb_data(key_str)); + + /* Samba/winbindd never needs this. */ + netsamlogon_clear_cached_user(sid); +} + +bool wcache_invalidate_cache(void) +{ + struct winbindd_domain *domain; + + for (domain = domain_list(); domain; domain = domain->next) { + struct winbind_cache *cache = get_cache(domain); + + DBG_DEBUG("wcache_invalidate_cache: invalidating cache " + "entries for %s\n", domain->name); + if (cache) { + if (cache->tdb) { + tdb_traverse(cache->tdb, traverse_fn, NULL); + } else { + return false; + } + } + } + return true; +} + +bool wcache_invalidate_cache_noinit(void) +{ + struct winbindd_domain *domain; + + for (domain = domain_list(); domain; domain = domain->next) { + struct winbind_cache *cache; + + /* Skip uninitialized domains. */ + if (!domain->initialized && !domain->internal) { + continue; + } + + cache = get_cache(domain); + + DBG_DEBUG("wcache_invalidate_cache: invalidating cache " + "entries for %s\n", domain->name); + if (cache) { + if (cache->tdb) { + tdb_traverse(cache->tdb, traverse_fn, NULL); + /* + * Flushing cache has nothing to with domains. + * return here if we successfully flushed once. + * To avoid unnecessary traversing the cache. + */ + return true; + } else { + return false; + } + } + } + return true; +} + +static bool init_wcache(void) +{ + char *db_path; + + if (wcache == NULL) { + wcache = SMB_XMALLOC_P(struct winbind_cache); + ZERO_STRUCTP(wcache); + } + + if (wcache->tdb != NULL) + return true; + + db_path = wcache_path(); + if (db_path == NULL) { + return false; + } + + /* when working offline we must not clear the cache on restart */ + wcache->tdb = tdb_open_log(db_path, + WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE, + TDB_INCOMPATIBLE_HASH | + (lp_winbind_offline_logon() ? TDB_DEFAULT : (TDB_DEFAULT | TDB_CLEAR_IF_FIRST)), + O_RDWR|O_CREAT, 0600); + TALLOC_FREE(db_path); + if (wcache->tdb == NULL) { + DBG_ERR("Failed to open winbindd_cache.tdb!\n"); + return false; + } + + return true; +} + +/************************************************************************ + This is called by the parent to initialize the cache file. + We don't need sophisticated locking here as we know we're the + only opener. +************************************************************************/ + +bool initialize_winbindd_cache(void) +{ + bool cache_bad = false; + uint32_t vers = 0; + bool ok; + + if (!init_wcache()) { + DBG_ERR("initialize_winbindd_cache: init_wcache failed.\n"); + return false; + } + + /* Check version number. */ + ok = tdb_fetch_uint32(wcache->tdb, WINBINDD_CACHE_VERSION_KEYSTR, &vers); + if (!ok) { + DBG_DEBUG("Failed to get cache version\n"); + cache_bad = true; + } + if (vers != WINBINDD_CACHE_VERSION) { + DBG_DEBUG("Invalid cache version %u != %u\n", + vers, + WINBINDD_CACHE_VERSION); + cache_bad = true; + } + + if (cache_bad) { + char *db_path; + + DBG_NOTICE("initialize_winbindd_cache: clearing cache " + "and re-creating with version number %d\n", + WINBINDD_CACHE_VERSION); + + tdb_close(wcache->tdb); + wcache->tdb = NULL; + + db_path = wcache_path(); + if (db_path == NULL) { + return false; + } + + if (unlink(db_path) == -1) { + DBG_ERR("initialize_winbindd_cache: unlink %s failed %s\n", + db_path, + strerror(errno) ); + TALLOC_FREE(db_path); + return false; + } + TALLOC_FREE(db_path); + if (!init_wcache()) { + DBG_ERR("initialize_winbindd_cache: re-initialization " + "init_wcache failed.\n"); + return false; + } + + /* Write the version. */ + if (!tdb_store_uint32(wcache->tdb, WINBINDD_CACHE_VERSION_KEYSTR, WINBINDD_CACHE_VERSION)) { + DBG_ERR("initialize_winbindd_cache: version number store failed %s\n", + tdb_errorstr(wcache->tdb) ); + return false; + } + } + + tdb_close(wcache->tdb); + wcache->tdb = NULL; + return true; +} + +void close_winbindd_cache(void) +{ + if (!wcache) { + return; + } + if (wcache->tdb) { + tdb_close(wcache->tdb); + wcache->tdb = NULL; + } +} + +bool lookup_cached_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid, + char **domain_name, char **name, + enum lsa_SidType *type) +{ + struct winbindd_domain *domain; + NTSTATUS status; + + domain = find_lookup_domain_from_sid(sid); + if (domain == NULL) { + return false; + } + status = wcache_sid_to_name(domain, sid, mem_ctx, domain_name, name, + type); + return NT_STATUS_IS_OK(status); +} + +bool lookup_cached_name(const char *namespace, + const char *domain_name, + const char *name, + struct dom_sid *sid, + enum lsa_SidType *type) +{ + struct winbindd_domain *domain; + NTSTATUS status; + bool original_online_state; + + domain = find_lookup_domain_from_name(namespace); + if (domain == NULL) { + return false; + } + + /* If we are doing a cached logon, temporarily set the domain + offline so the cache won't expire the entry */ + + original_online_state = domain->online; + domain->online = false; + status = wcache_name_to_sid(domain, domain_name, name, sid, type); + domain->online = original_online_state; + + return NT_STATUS_IS_OK(status); +} + +/* + * Cache a name to sid without checking the sequence number. + * Used when caching from a trusted PAC. + */ + +void cache_name2sid_trusted(struct winbindd_domain *domain, + const char *domain_name, + const char *name, + enum lsa_SidType type, + const struct dom_sid *sid) +{ + /* + * Ensure we store the mapping with the + * existing sequence number from the cache. + */ + get_cache(domain); + (void)fetch_cache_seqnum(domain, time(NULL)); + wcache_save_name_to_sid(domain, + NT_STATUS_OK, + domain_name, + name, + sid, + type); +} + +void cache_name2sid(struct winbindd_domain *domain, + const char *domain_name, const char *name, + enum lsa_SidType type, const struct dom_sid *sid) +{ + refresh_sequence_number(domain); + wcache_save_name_to_sid(domain, NT_STATUS_OK, domain_name, name, + sid, type); +} + +/* + * The original idea that this cache only contains centries has + * been blurred - now other stuff gets put in here. Ensure we + * ignore these things on cleanup. + */ + +static int traverse_fn_cleanup(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, + TDB_DATA dbuf, void *state) +{ + struct cache_entry *centry; + + if (is_non_centry_key(kbuf)) { + return 0; + } + + centry = wcache_fetch_raw((char *)kbuf.dptr); + if (!centry) { + return 0; + } + + if (!NT_STATUS_IS_OK(centry->status)) { + DBG_DEBUG("deleting centry %s\n", (const char *)kbuf.dptr); + tdb_delete(the_tdb, kbuf); + } + + centry_free(centry); + return 0; +} + +/* flush the cache */ +static void wcache_flush_cache(void) +{ + char *db_path; + + if (!wcache) + return; + if (wcache->tdb) { + tdb_close(wcache->tdb); + wcache->tdb = NULL; + } + if (!winbindd_use_cache()) { + return; + } + + db_path = wcache_path(); + if (db_path == NULL) { + return; + } + + /* when working offline we must not clear the cache on restart */ + wcache->tdb = tdb_open_log(db_path, + WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE, + TDB_INCOMPATIBLE_HASH | + (lp_winbind_offline_logon() ? TDB_DEFAULT : (TDB_DEFAULT | TDB_CLEAR_IF_FIRST)), + O_RDWR|O_CREAT, 0600); + TALLOC_FREE(db_path); + if (!wcache->tdb) { + DBG_ERR("Failed to open winbindd_cache.tdb!\n"); + return; + } + + tdb_traverse(wcache->tdb, traverse_fn_cleanup, NULL); + + DBG_DEBUG("wcache_flush_cache success\n"); +} + +/* Count cached creds */ + +static int traverse_fn_cached_creds(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, + void *state) +{ + int *cred_count = (int*)state; + + if (strncmp((const char *)kbuf.dptr, "CRED/", 5) == 0) { + (*cred_count)++; + } + return 0; +} + +NTSTATUS wcache_count_cached_creds(struct winbindd_domain *domain, int *count) +{ + struct winbind_cache *cache = get_cache(domain); + + *count = 0; + + if (!cache->tdb) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + tdb_traverse(cache->tdb, traverse_fn_cached_creds, (void *)count); + + return NT_STATUS_OK; +} + +struct cred_list { + struct cred_list *prev, *next; + TDB_DATA key; + fstring name; + time_t created; +}; +static struct cred_list *wcache_cred_list; + +static int traverse_fn_get_credlist(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, + void *state) +{ + struct cred_list *cred; + + if (strncmp((const char *)kbuf.dptr, "CRED/", 5) == 0) { + + cred = SMB_MALLOC_P(struct cred_list); + if (cred == NULL) { + DBG_ERR("traverse_fn_remove_first_creds: failed to malloc new entry for list\n"); + return -1; + } + + ZERO_STRUCTP(cred); + + /* save a copy of the key */ + + fstrcpy(cred->name, (const char *)kbuf.dptr); + DLIST_ADD(wcache_cred_list, cred); + } + + return 0; +} + +NTSTATUS wcache_remove_oldest_cached_creds(struct winbindd_domain *domain, const struct dom_sid *sid) +{ + struct winbind_cache *cache = get_cache(domain); + NTSTATUS status; + int ret; + struct cred_list *cred, *next, *oldest = NULL; + + if (!cache->tdb) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + /* we possibly already have an entry */ + if (sid && NT_STATUS_IS_OK(wcache_cached_creds_exist(domain, sid))) { + + fstring key_str; + struct dom_sid_buf tmp; + + DBG_DEBUG("we already have an entry, deleting that\n"); + + fstr_sprintf(key_str, "CRED/%s", dom_sid_str_buf(sid, &tmp)); + + tdb_delete(cache->tdb, string_tdb_data(key_str)); + + return NT_STATUS_OK; + } + + ret = tdb_traverse(cache->tdb, traverse_fn_get_credlist, NULL); + if (ret == 0) { + return NT_STATUS_OK; + } else if ((ret < 0) || (wcache_cred_list == NULL)) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + ZERO_STRUCTP(oldest); + + for (cred = wcache_cred_list; cred; cred = cred->next) { + + TDB_DATA data; + time_t t; + + data = tdb_fetch(cache->tdb, string_tdb_data(cred->name)); + if (!data.dptr) { + DBG_DEBUG("wcache_remove_oldest_cached_creds: entry for [%s] not found\n", + cred->name); + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto done; + } + + t = IVAL(data.dptr, 0); + SAFE_FREE(data.dptr); + + if (!oldest) { + oldest = SMB_MALLOC_P(struct cred_list); + if (oldest == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + fstrcpy(oldest->name, cred->name); + oldest->created = t; + continue; + } + + if (t < oldest->created) { + fstrcpy(oldest->name, cred->name); + oldest->created = t; + } + } + + if (tdb_delete(cache->tdb, string_tdb_data(oldest->name)) == 0) { + status = NT_STATUS_OK; + } else { + status = NT_STATUS_UNSUCCESSFUL; + } +done: + for (cred = wcache_cred_list; cred; cred = next) { + next = cred->next; + DLIST_REMOVE(wcache_cred_list, cred); + SAFE_FREE(cred); + } + SAFE_FREE(oldest); + + return status; +} + +/* Change the global online/offline state. */ +bool set_global_winbindd_state_offline(void) +{ + bool ok; + uint8_t buf[4] = {0}; + TDB_DATA data = { + .dptr = buf, + .dsize = sizeof(buf) + }; + int rc; + + DBG_NOTICE("Offline requested\n"); + + if (wcache == NULL || wcache->tdb == NULL) { + DBG_NOTICE("Winbind cache doesn't exist yet\n"); + return false; + } + + if (!lp_winbind_offline_logon()) { + DBG_DEBUG("Rejecting request to set winbind offline, " + "offline logons are disabled in smb.conf\n"); + return false; + } + + ok = get_global_winbindd_state_offline(); + if (ok) { + return true; + } + + PUSH_LE_U32(buf, 0, time(NULL)); + + rc = tdb_store_bystring(wcache->tdb, + "WINBINDD_OFFLINE", + data, + TDB_INSERT); + if (rc != 0) { + return false; + } + + return true; + +} + +void set_global_winbindd_state_online(void) +{ + DBG_DEBUG("set_global_winbindd_state_online: online requested.\n"); + + if (!lp_winbind_offline_logon()) { + DBG_DEBUG("Rejecting request to set winbind online, " + "offline logons are disabled in smb.conf.\n"); + return; + } + + if (!wcache->tdb) { + return; + } + + /* Ensure there is no key "WINBINDD_OFFLINE" in the cache tdb. */ + tdb_delete_bystring(wcache->tdb, "WINBINDD_OFFLINE"); +} + +bool get_global_winbindd_state_offline(void) +{ + TDB_DATA data; + + data = tdb_fetch_bystring(wcache->tdb, "WINBINDD_OFFLINE"); + if (data.dptr == NULL || data.dsize != 4) { + DBG_DEBUG("Offline state not set.\n"); + SAFE_FREE(data.dptr); + return false; + } + + return true; +} + +/*********************************************************************** + Validate functions for all possible cache tdb keys. +***********************************************************************/ + +static struct cache_entry *create_centry_validate(const char *kstr, TDB_DATA data, + struct tdb_validation_status *state) +{ + struct cache_entry *centry; + + centry = SMB_XMALLOC_P(struct cache_entry); + centry->data = (unsigned char *)smb_memdup(data.dptr, data.dsize); + if (!centry->data) { + SAFE_FREE(centry); + return NULL; + } + centry->len = data.dsize; + centry->ofs = 0; + + if (centry->len < 16) { + /* huh? corrupt cache? */ + DBG_ERR("create_centry_validate: Corrupt cache for key %s " + "(len < 16) ?\n", kstr); + centry_free(centry); + state->bad_entry = true; + state->success = false; + return NULL; + } + + centry->status = NT_STATUS(centry_uint32(centry)); + centry->sequence_number = centry_uint32(centry); + centry->timeout = centry_uint64_t(centry); + return centry; +} + +static int validate_seqnum(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + if (dbuf.dsize != 8) { + DBG_ERR("validate_seqnum: Corrupt cache for key %s (len %u != 8) ?\n", + keystr, (unsigned int)dbuf.dsize ); + state->bad_entry = true; + return 1; + } + return 0; +} + +static int validate_u(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); + struct dom_sid sid; + + if (!centry) { + return 1; + } + + (void)centry_string(centry, mem_ctx); + (void)centry_string(centry, mem_ctx); + (void)centry_string(centry, mem_ctx); + (void)centry_string(centry, mem_ctx); + (void)centry_string(centry, mem_ctx); + (void)centry_uint32(centry); + (void)centry_uint32(centry); + (void)centry_string(centry, mem_ctx); + (void)centry_sid(centry, &sid); + (void)centry_sid(centry, &sid); + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_u: %s ok\n", keystr); + return 0; +} + +static int validate_loc_pol(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_nttime(centry); + (void)centry_nttime(centry); + (void)centry_uint16(centry); + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_loc_pol: %s ok\n", keystr); + return 0; +} + +static int validate_pwd_pol(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_uint16(centry); + (void)centry_uint16(centry); + (void)centry_uint32(centry); + (void)centry_nttime(centry); + (void)centry_nttime(centry); + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_pwd_pol: %s ok\n", keystr); + return 0; +} + +static int validate_cred(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_time(centry); + (void)centry_hash16(centry, mem_ctx); + + /* We only have 17 bytes more data in the salted cred case. */ + if (centry->len - centry->ofs == 17) { + (void)centry_hash16(centry, mem_ctx); + } + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_cred: %s ok\n", keystr); + return 0; +} + +static int validate_ul(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); + int32_t num_entries, i; + + if (!centry) { + return 1; + } + + num_entries = (int32_t)centry_uint32(centry); + + for (i=0; i< num_entries; i++) { + (void)centry_uint32(centry); + } + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_ul: %s ok\n", keystr); + return 0; +} + +static int validate_gl(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); + int32_t num_entries, i; + + if (!centry) { + return 1; + } + + num_entries = centry_uint32(centry); + + for (i=0; i< num_entries; i++) { + (void)centry_string(centry, mem_ctx); + (void)centry_string(centry, mem_ctx); + (void)centry_uint32(centry); + } + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_gl: %s ok\n", keystr); + return 0; +} + +static int validate_ug(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); + int32_t num_groups, i; + + if (!centry) { + return 1; + } + + num_groups = centry_uint32(centry); + + for (i=0; i< num_groups; i++) { + struct dom_sid sid; + centry_sid(centry, &sid); + } + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_ug: %s ok\n", keystr); + return 0; +} + +static int validate_ua(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); + int32_t num_aliases, i; + + if (!centry) { + return 1; + } + + num_aliases = centry_uint32(centry); + + for (i=0; i < num_aliases; i++) { + (void)centry_uint32(centry); + } + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_ua: %s ok\n", keystr); + return 0; +} + +static int validate_gm(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); + int32_t num_names, i; + + if (!centry) { + return 1; + } + + num_names = centry_uint32(centry); + + for (i=0; i< num_names; i++) { + struct dom_sid sid; + centry_sid(centry, &sid); + (void)centry_string(centry, mem_ctx); + (void)centry_uint32(centry); + } + + centry_free(centry); + + if (!(state->success)) { + return 1; + } + DBG_DEBUG("validate_gm: %s ok\n", keystr); + return 0; +} + +static int validate_dr(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + /* Can't say anything about this other than must be nonzero. */ + if (dbuf.dsize == 0) { + DBG_ERR("validate_dr: Corrupt cache for key %s (len == 0) ?\n", + keystr); + state->bad_entry = true; + state->success = false; + return 1; + } + + DBG_DEBUG("validate_dr: %s ok\n", keystr); + return 0; +} + +static int validate_de(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + /* Can't say anything about this other than must be nonzero. */ + if (dbuf.dsize == 0) { + DBG_ERR("validate_de: Corrupt cache for key %s (len == 0) ?\n", + keystr); + state->bad_entry = true; + state->success = false; + return 1; + } + + DBG_DEBUG("validate_de: %s ok\n", 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; + } + DBG_DEBUG("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; + } + DBG_DEBUG("%s ok\n", keystr); + return 0; +} + +static int validate_trustdomcache(TALLOC_CTX *mem_ctx, const char *keystr, + TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + if (dbuf.dsize == 0) { + DBG_ERR("validate_trustdomcache: Corrupt cache for " + "key %s (len ==0) ?\n", keystr); + state->bad_entry = true; + state->success = false; + return 1; + } + + DBG_DEBUG("validate_trustdomcache: %s ok\n" + " Don't trust me, I am a DUMMY!\n", + keystr); + return 0; +} + +static int validate_offline(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + if (dbuf.dsize != 4) { + DBG_ERR("validate_offline: Corrupt cache for key %s (len %u != 4) ?\n", + keystr, (unsigned int)dbuf.dsize ); + state->bad_entry = true; + state->success = false; + return 1; + } + DBG_DEBUG("validate_offline: %s ok\n", keystr); + return 0; +} + +static int validate_ndr(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + /* + * Ignore validation for now. The proper way to do this is with a + * checksum. Just pure parsing does not really catch much. + */ + return 0; +} + +static int validate_cache_version(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, + struct tdb_validation_status *state) +{ + if (dbuf.dsize != 4) { + DBG_ERR("validate_cache_version: Corrupt cache for " + "key %s (len %u != 4) ?\n", + keystr, (unsigned int)dbuf.dsize); + state->bad_entry = true; + state->success = false; + return 1; + } + + DBG_DEBUG("validate_cache_version: %s ok\n", keystr); + return 0; +} + +/*********************************************************************** + A list of all possible cache tdb keys with associated validation + functions. +***********************************************************************/ + +struct key_val_struct { + const char *keyname; + int (*validate_data_fn)(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status* state); +} key_val[] = { + {"SEQNUM/", validate_seqnum}, + {"U/", validate_u}, + {"LOC_POL/", validate_loc_pol}, + {"PWD_POL/", validate_pwd_pol}, + {"CRED/", validate_cred}, + {"UL/", validate_ul}, + {"GL/", validate_gl}, + {"UG/", validate_ug}, + {"UA", validate_ua}, + {"GM/", validate_gm}, + {"DR/", validate_dr}, + {"DE/", validate_de}, + {"TRUSTDOMCACHE/", validate_trustdomcache}, + {"NSS/NA/", validate_nss_na}, + {"NSS/AN/", validate_nss_an}, + {"WINBINDD_OFFLINE", validate_offline}, + {"NDR/", validate_ndr}, + {WINBINDD_CACHE_VERSION_KEYSTR, validate_cache_version}, + {NULL, NULL} +}; + +/*********************************************************************** + Function to look at every entry in the tdb and validate it as far as + possible. +***********************************************************************/ + +static int cache_traverse_validate_fn(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + int i; + unsigned int max_key_len = 1024; + struct tdb_validation_status *v_state = (struct tdb_validation_status *)state; + + /* Paranoia check. */ + if (strncmp("UA/", (const char *)kbuf.dptr, 3) == 0 || + strncmp("NDR/", (const char *)kbuf.dptr, 4) == 0) { + max_key_len = 1024 * 1024; + } + if (kbuf.dsize > max_key_len) { + DBG_ERR("cache_traverse_validate_fn: key length too large: " + "(%u) > (%u)\n\n", + (unsigned int)kbuf.dsize, (unsigned int)max_key_len); + return 1; + } + + for (i = 0; key_val[i].keyname; i++) { + size_t namelen = strlen(key_val[i].keyname); + if (kbuf.dsize >= namelen && ( + strncmp(key_val[i].keyname, (const char *)kbuf.dptr, namelen)) == 0) { + TALLOC_CTX *mem_ctx; + char *keystr; + int ret; + + keystr = SMB_MALLOC_ARRAY(char, kbuf.dsize+1); + if (!keystr) { + return 1; + } + memcpy(keystr, kbuf.dptr, kbuf.dsize); + keystr[kbuf.dsize] = '\0'; + + mem_ctx = talloc_init("validate_ctx"); + if (!mem_ctx) { + SAFE_FREE(keystr); + return 1; + } + + ret = key_val[i].validate_data_fn(mem_ctx, keystr, dbuf, + v_state); + + SAFE_FREE(keystr); + talloc_destroy(mem_ctx); + return ret; + } + } + + DBG_ERR("cache_traverse_validate_fn: unknown cache entry\nkey :\n"); + dump_data(0, (uint8_t *)kbuf.dptr, kbuf.dsize); + DBG_ERR("data :\n"); + dump_data(0, (uint8_t *)dbuf.dptr, dbuf.dsize); + v_state->unknown_key = true; + v_state->success = false; + return 1; /* terminate. */ +} + +static void validate_panic(const char *const why) +{ + DBG_ERR("validating cache: would panic %s\n" + "exiting instead (cache validation mode)\n", why ); + exit(47); +} + +static int wbcache_update_centry_fn(TDB_CONTEXT *tdb, + TDB_DATA key, + TDB_DATA data, + void *state) +{ + uint64_t ctimeout; + TDB_DATA blob; + + if (is_non_centry_key(key)) { + return 0; + } + + if (data.dptr == NULL || data.dsize == 0) { + if (tdb_delete(tdb, key) < 0) { + DBG_ERR("tdb_delete for [%s] failed!\n", + key.dptr); + return 1; + } + } + + /* add timeout to blob (uint64_t) */ + blob.dsize = data.dsize + 8; + + blob.dptr = SMB_XMALLOC_ARRAY(uint8_t, blob.dsize); + if (blob.dptr == NULL) { + return 1; + } + memset(blob.dptr, 0, blob.dsize); + + /* copy status and seqnum */ + memcpy(blob.dptr, data.dptr, 8); + + /* add timeout */ + ctimeout = lp_winbind_cache_time() + time(NULL); + SBVAL(blob.dptr, 8, ctimeout); + + /* copy the rest */ + memcpy(blob.dptr + 16, data.dptr + 8, data.dsize - 8); + + if (tdb_store(tdb, key, blob, TDB_REPLACE) < 0) { + DBG_ERR("tdb_store to update [%s] failed!\n", + key.dptr); + SAFE_FREE(blob.dptr); + return 1; + } + + SAFE_FREE(blob.dptr); + return 0; +} + +static bool wbcache_upgrade_v1_to_v2(TDB_CONTEXT *tdb) +{ + int rc; + + DBG_NOTICE("Upgrade to version 2 of the winbindd_cache.tdb\n"); + + rc = tdb_traverse(tdb, wbcache_update_centry_fn, NULL); + if (rc < 0) { + return false; + } + + return true; +} + +/*********************************************************************** + Try and validate every entry in the winbindd cache. If we fail here, + delete the cache tdb and return non-zero. +***********************************************************************/ + +int winbindd_validate_cache(void) +{ + int ret = -1; + char *tdb_path = NULL; + TDB_CONTEXT *tdb = NULL; + uint32_t vers_id; + bool ok; + + DBG_DEBUG("winbindd_validate_cache: replacing panic function\n"); + smb_panic_fn = validate_panic; + + tdb_path = wcache_path(); + if (tdb_path == NULL) { + goto done; + } + + tdb = tdb_open_log(tdb_path, + WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE, + TDB_INCOMPATIBLE_HASH | + ( lp_winbind_offline_logon() + ? TDB_DEFAULT + : TDB_DEFAULT | TDB_CLEAR_IF_FIRST ), + O_RDWR|O_CREAT, + 0600); + if (!tdb) { + DBG_ERR("winbindd_validate_cache: " + "error opening/initializing tdb\n"); + goto done; + } + + /* Version check and upgrade code. */ + if (!tdb_fetch_uint32(tdb, WINBINDD_CACHE_VERSION_KEYSTR, &vers_id)) { + DBG_DEBUG("Fresh database\n"); + tdb_store_uint32(tdb, WINBINDD_CACHE_VERSION_KEYSTR, WINBINDD_CACHE_VERSION); + vers_id = WINBINDD_CACHE_VERSION; + } + + if (vers_id != WINBINDD_CACHE_VERSION) { + if (vers_id == WINBINDD_CACHE_VER1) { + ok = wbcache_upgrade_v1_to_v2(tdb); + if (!ok) { + DBG_DEBUG("winbindd_validate_cache: upgrade to version 2 failed.\n"); + unlink(tdb_path); + goto done; + } + + tdb_store_uint32(tdb, + WINBINDD_CACHE_VERSION_KEYSTR, + WINBINDD_CACHE_VERSION); + vers_id = WINBINDD_CACHE_VER2; + } + } + + tdb_close(tdb); + + ret = tdb_validate_and_backup(tdb_path, cache_traverse_validate_fn); + + if (ret != 0) { + DBG_DEBUG("winbindd_validate_cache: validation not successful.\n" + "removing tdb %s.\n", tdb_path); + unlink(tdb_path); + } + +done: + TALLOC_FREE(tdb_path); + DBG_DEBUG("winbindd_validate_cache: restoring panic function\n"); + smb_panic_fn = smb_panic; + return ret; +} + +/*********************************************************************** + Try and validate every entry in the winbindd cache. +***********************************************************************/ + +int winbindd_validate_cache_nobackup(void) +{ + int ret = -1; + char *tdb_path; + + DBG_DEBUG("winbindd_validate_cache: replacing panic function\n"); + smb_panic_fn = validate_panic; + + tdb_path = wcache_path(); + if (tdb_path == NULL) { + goto err_panic_restore; + } + + if (wcache == NULL || wcache->tdb == NULL) { + ret = tdb_validate_open(tdb_path, cache_traverse_validate_fn); + } else { + ret = tdb_validate(wcache->tdb, cache_traverse_validate_fn); + } + + if (ret != 0) { + DBG_DEBUG("winbindd_validate_cache_nobackup: validation not " + "successful.\n"); + } + + TALLOC_FREE(tdb_path); +err_panic_restore: + DBG_DEBUG("winbindd_validate_cache_nobackup: restoring panic " + "function\n"); + smb_panic_fn = smb_panic; + return ret; +} + +bool winbindd_cache_validate_and_initialize(void) +{ + close_winbindd_cache(); + + if (lp_winbind_offline_logon()) { + if (winbindd_validate_cache() < 0) { + DBG_ERR("winbindd cache tdb corrupt and no backup " + "could be restored.\n"); + } + } + + return initialize_winbindd_cache(); +} + +/********************************************************************* + ********************************************************************/ + +static bool add_wbdomain_to_tdc_array( struct winbindd_domain *new_dom, + struct winbindd_tdc_domain **domains, + size_t *num_domains ) +{ + struct winbindd_tdc_domain *list = NULL; + size_t i, idx; + bool set_only = false; + + /* don't allow duplicates */ + + idx = *num_domains; + list = *domains; + + for ( i=0; i< (*num_domains); i++ ) { + if ( strequal( new_dom->name, list[i].domain_name ) ) { + DBG_DEBUG("add_wbdomain_to_tdc_array: Found existing record for %s\n", + new_dom->name); + idx = i; + set_only = true; + + break; + } + } + + if ( !set_only ) { + if ( !*domains ) { + list = talloc_array( NULL, struct winbindd_tdc_domain, 1 ); + idx = 0; + } else { + list = talloc_realloc( *domains, *domains, + struct winbindd_tdc_domain, + (*num_domains)+1); + idx = *num_domains; + } + + ZERO_STRUCT( list[idx] ); + } + + if ( !list ) + return false; + + list[idx].domain_name = talloc_strdup(list, new_dom->name); + if (list[idx].domain_name == NULL) { + return false; + } + if (new_dom->alt_name != NULL) { + list[idx].dns_name = talloc_strdup(list, new_dom->alt_name); + if (list[idx].dns_name == NULL) { + return false; + } + } + + if ( !is_null_sid( &new_dom->sid ) ) { + sid_copy( &list[idx].sid, &new_dom->sid ); + } else { + sid_copy(&list[idx].sid, &global_sid_NULL); + } + + if ( new_dom->domain_flags != 0x0 ) + list[idx].trust_flags = new_dom->domain_flags; + + if ( new_dom->domain_type != 0x0 ) + list[idx].trust_type = new_dom->domain_type; + + if ( new_dom->domain_trust_attribs != 0x0 ) + list[idx].trust_attribs = new_dom->domain_trust_attribs; + + if ( !set_only ) { + *domains = list; + *num_domains = idx + 1; + } + + return true; +} + +/********************************************************************* + ********************************************************************/ + +static TDB_DATA make_tdc_key( const char *domain_name ) +{ + char *keystr = NULL; + TDB_DATA key = { NULL, 0 }; + + if ( !domain_name ) { + DBG_INFO("make_tdc_key: Keyname workgroup is NULL!\n"); + return key; + } + + if (asprintf( &keystr, "TRUSTDOMCACHE/%s", domain_name ) == -1) { + return key; + } + key = string_term_tdb_data(keystr); + + return key; +} + +/********************************************************************* + ********************************************************************/ + +static int pack_tdc_domains( struct winbindd_tdc_domain *domains, + size_t num_domains, + unsigned char **buf ) +{ + unsigned char *buffer = NULL; + int len = 0; + int buflen = 0; + size_t i = 0; + + DBG_DEBUG("pack_tdc_domains: Packing %d trusted domains\n", + (int)num_domains); + + buflen = 0; + + again: + len = 0; + + /* Store the number of array items first */ + len += tdb_pack( buffer ? buffer+len : NULL, + buffer ? buflen-len : 0, "d", + num_domains ); + + /* now pack each domain trust record */ + for ( i=0; i<num_domains; i++ ) { + + struct dom_sid_buf tmp; + + if ( buflen > 0 ) { + DBG_DEBUG("pack_tdc_domains: Packing domain %s (%s)\n", + domains[i].domain_name, + domains[i].dns_name ? domains[i].dns_name : "UNKNOWN" ); + } + + len += tdb_pack( buffer ? buffer+len : NULL, + buffer ? buflen-len : 0, "fffddd", + domains[i].domain_name, + domains[i].dns_name ? domains[i].dns_name : "", + dom_sid_str_buf(&domains[i].sid, &tmp), + domains[i].trust_flags, + domains[i].trust_attribs, + domains[i].trust_type ); + } + + if ( buflen < len ) { + SAFE_FREE(buffer); + if ( (buffer = SMB_MALLOC_ARRAY(unsigned char, len)) == NULL ) { + DBG_ERR("pack_tdc_domains: failed to alloc buffer!\n"); + buflen = -1; + goto done; + } + buflen = len; + goto again; + } + + *buf = buffer; + + done: + return buflen; +} + +/********************************************************************* + ********************************************************************/ + +static size_t unpack_tdc_domains( unsigned char *buf, int buflen, + struct winbindd_tdc_domain **domains ) +{ + fstring domain_name, dns_name, sid_string; + uint32_t type, attribs, flags; + int num_domains; + int len = 0; + int i; + struct winbindd_tdc_domain *list = NULL; + + /* get the number of domains */ + len += tdb_unpack( buf+len, buflen-len, "d", &num_domains); + if ( len == -1 ) { + DBG_INFO("unpack_tdc_domains: Failed to unpack domain array\n"); + return 0; + } + + list = talloc_array( NULL, struct winbindd_tdc_domain, num_domains ); + if ( !list ) { + DBG_ERR("unpack_tdc_domains: Failed to talloc() domain list!\n"); + return 0; + } + + for ( i=0; i<num_domains; i++ ) { + int this_len; + + this_len = tdb_unpack( buf+len, buflen-len, "fffddd", + domain_name, + dns_name, + sid_string, + &flags, + &attribs, + &type ); + + if ( this_len == -1 ) { + DBG_INFO("unpack_tdc_domains: Failed to unpack domain array\n"); + TALLOC_FREE( list ); + return 0; + } + len += this_len; + + DBG_DEBUG("unpack_tdc_domains: Unpacking domain %s (%s) " + "SID %s, flags = 0x%x, attribs = 0x%x, type = 0x%x\n", + domain_name, dns_name, sid_string, + flags, attribs, type); + + list[i].domain_name = talloc_strdup( list, domain_name ); + list[i].dns_name = NULL; + if (dns_name[0] != '\0') { + list[i].dns_name = talloc_strdup(list, dns_name); + } + if ( !string_to_sid( &(list[i].sid), sid_string ) ) { + DBG_DEBUG("unpack_tdc_domains: no SID for domain %s\n", + domain_name); + } + list[i].trust_flags = flags; + list[i].trust_attribs = attribs; + list[i].trust_type = type; + } + + *domains = list; + + return num_domains; +} + +/********************************************************************* + ********************************************************************/ + +static bool wcache_tdc_store_list( struct winbindd_tdc_domain *domains, size_t num_domains ) +{ + TDB_DATA key = make_tdc_key( lp_workgroup() ); + TDB_DATA data = { NULL, 0 }; + int ret; + + if ( !key.dptr ) + return false; + + /* See if we were asked to delete the cache entry */ + + if ( !domains ) { + ret = tdb_delete( wcache->tdb, key ); + goto done; + } + + data.dsize = pack_tdc_domains( domains, num_domains, &data.dptr ); + + if ( !data.dptr ) { + ret = -1; + goto done; + } + + ret = tdb_store( wcache->tdb, key, data, 0 ); + + done: + SAFE_FREE( data.dptr ); + SAFE_FREE( key.dptr ); + + return ( ret == 0 ); +} + +/********************************************************************* + ********************************************************************/ + +bool wcache_tdc_fetch_list( struct winbindd_tdc_domain **domains, size_t *num_domains ) +{ + TDB_DATA key = make_tdc_key( lp_workgroup() ); + TDB_DATA data = { NULL, 0 }; + + *domains = NULL; + *num_domains = 0; + + if ( !key.dptr ) + return false; + + data = tdb_fetch( wcache->tdb, key ); + + SAFE_FREE( key.dptr ); + + if ( !data.dptr ) + return false; + + *num_domains = unpack_tdc_domains( data.dptr, data.dsize, domains ); + + SAFE_FREE( data.dptr ); + + if ( !*domains ) + return false; + + return true; +} + +/********************************************************************* + ********************************************************************/ + +bool wcache_tdc_add_domain( struct winbindd_domain *domain ) +{ + struct winbindd_tdc_domain *dom_list = NULL; + size_t num_domains = 0; + bool ret = false; + struct dom_sid_buf buf; + + DBG_DEBUG("wcache_tdc_add_domain: Adding domain %s (%s), SID %s, " + "flags = 0x%x, attributes = 0x%x, type = 0x%x\n", + domain->name, domain->alt_name, + dom_sid_str_buf(&domain->sid, &buf), + domain->domain_flags, + domain->domain_trust_attribs, + domain->domain_type); + + if ( !init_wcache() ) { + return false; + } + + /* fetch the list */ + + wcache_tdc_fetch_list( &dom_list, &num_domains ); + + /* add the new domain */ + + if ( !add_wbdomain_to_tdc_array( domain, &dom_list, &num_domains ) ) { + goto done; + } + + /* pack the domain */ + + if ( !wcache_tdc_store_list( dom_list, num_domains ) ) { + goto done; + } + + /* Success */ + + ret = true; + done: + TALLOC_FREE( dom_list ); + + return ret; +} + +static struct winbindd_tdc_domain *wcache_tdc_dup_domain( + TALLOC_CTX *mem_ctx, const struct winbindd_tdc_domain *src) +{ + struct winbindd_tdc_domain *dst; + + dst = talloc(mem_ctx, struct winbindd_tdc_domain); + if (dst == NULL) { + goto fail; + } + dst->domain_name = talloc_strdup(dst, src->domain_name); + if (dst->domain_name == NULL) { + goto fail; + } + + dst->dns_name = NULL; + if (src->dns_name != NULL) { + dst->dns_name = talloc_strdup(dst, src->dns_name); + if (dst->dns_name == NULL) { + goto fail; + } + } + + sid_copy(&dst->sid, &src->sid); + dst->trust_flags = src->trust_flags; + dst->trust_type = src->trust_type; + dst->trust_attribs = src->trust_attribs; + return dst; +fail: + TALLOC_FREE(dst); + return NULL; +} + +/********************************************************************* + ********************************************************************/ + +struct winbindd_tdc_domain * wcache_tdc_fetch_domain( TALLOC_CTX *ctx, const char *name ) +{ + struct winbindd_tdc_domain *dom_list = NULL; + size_t num_domains = 0; + size_t i; + struct winbindd_tdc_domain *d = NULL; + + DBG_DEBUG("wcache_tdc_fetch_domain: Searching for domain %s\n", name); + + if ( !init_wcache() ) { + return NULL; + } + + /* fetch the list */ + + wcache_tdc_fetch_list( &dom_list, &num_domains ); + + for ( i=0; i<num_domains; i++ ) { + if ( strequal(name, dom_list[i].domain_name) || + strequal(name, dom_list[i].dns_name) ) + { + DBG_DEBUG("wcache_tdc_fetch_domain: Found domain %s\n", + name); + + d = wcache_tdc_dup_domain(ctx, &dom_list[i]); + break; + } + } + + TALLOC_FREE( dom_list ); + + return d; +} + +/********************************************************************* + ********************************************************************/ + +void wcache_tdc_clear( void ) +{ + if ( !init_wcache() ) + return; + + wcache_tdc_store_list( NULL, 0 ); + + return; +} + +static bool wcache_ndr_key(TALLOC_CTX *mem_ctx, const char *domain_name, + uint32_t opnum, const DATA_BLOB *req, + TDB_DATA *pkey) +{ + char *key; + size_t keylen; + + key = talloc_asprintf(mem_ctx, "NDR/%s/%d/", domain_name, (int)opnum); + if (key == NULL) { + return false; + } + keylen = talloc_get_size(key) - 1; + + key = talloc_realloc(mem_ctx, key, char, keylen + req->length); + if (key == NULL) { + return false; + } + memcpy(key + keylen, req->data, req->length); + + pkey->dptr = (uint8_t *)key; + pkey->dsize = talloc_get_size(key); + return true; +} + +static bool wcache_opnum_cacheable(uint32_t opnum) +{ + switch (opnum) { + case NDR_WBINT_LOOKUPSID: + case NDR_WBINT_LOOKUPSIDS: + case NDR_WBINT_LOOKUPNAME: + case NDR_WBINT_SIDS2UNIXIDS: + case NDR_WBINT_UNIXIDS2SIDS: + case NDR_WBINT_GETNSSINFO: + case NDR_WBINT_LOOKUPUSERALIASES: + case NDR_WBINT_LOOKUPUSERGROUPS: + case NDR_WBINT_LOOKUPGROUPMEMBERS: + case NDR_WBINT_QUERYGROUPLIST: + case NDR_WBINT_QUERYUSERRIDLIST: + case NDR_WBINT_DSGETDCNAME: + case NDR_WBINT_LOOKUPRIDS: + return true; + } + return false; +} + +bool wcache_fetch_ndr(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, + uint32_t opnum, const DATA_BLOB *req, DATA_BLOB *resp) +{ + TDB_DATA key, data; + bool ret = false; + + if (!wcache_opnum_cacheable(opnum) || + is_my_own_sam_domain(domain) || + is_builtin_domain(domain)) { + return false; + } + + if (wcache->tdb == NULL) { + return false; + } + + if (!wcache_ndr_key(talloc_tos(), domain->name, opnum, req, &key)) { + return false; + } + data = tdb_fetch(wcache->tdb, key); + TALLOC_FREE(key.dptr); + + if (data.dptr == NULL) { + return false; + } + if (data.dsize < 12) { + goto fail; + } + + if (is_domain_online(domain)) { + uint32_t entry_seqnum, dom_seqnum, last_check; + uint64_t entry_timeout; + + if (!wcache_fetch_seqnum(domain->name, &dom_seqnum, + &last_check)) { + goto fail; + } + entry_seqnum = IVAL(data.dptr, 0); + if (entry_seqnum != dom_seqnum) { + DBG_DEBUG("Entry has wrong sequence number: %d\n", + (int)entry_seqnum); + goto fail; + } + entry_timeout = BVAL(data.dptr, 4); + if (time(NULL) > (time_t)entry_timeout) { + DBG_DEBUG("Entry has timed out\n"); + goto fail; + } + } + + resp->data = (uint8_t *)talloc_memdup(mem_ctx, data.dptr + 12, + data.dsize - 12); + if (resp->data == NULL) { + DBG_DEBUG("talloc failed\n"); + goto fail; + } + resp->length = data.dsize - 12; + + ret = true; +fail: + SAFE_FREE(data.dptr); + return ret; +} + +void wcache_store_ndr(struct winbindd_domain *domain, uint32_t opnum, + const DATA_BLOB *req, const DATA_BLOB *resp) +{ + TDB_DATA key, data; + uint32_t dom_seqnum, last_check; + uint64_t timeout; + + if (!wcache_opnum_cacheable(opnum) || + is_my_own_sam_domain(domain) || + is_builtin_domain(domain)) { + return; + } + + if (wcache->tdb == NULL) { + return; + } + + if (!wcache_fetch_seqnum(domain->name, &dom_seqnum, &last_check)) { + DBG_DEBUG("could not fetch seqnum for domain %s\n", + domain->name); + return; + } + + if (!wcache_ndr_key(talloc_tos(), domain->name, opnum, req, &key)) { + return; + } + + timeout = time(NULL) + lp_winbind_cache_time(); + + data.dsize = resp->length + 12; + data.dptr = talloc_array(key.dptr, uint8_t, data.dsize); + if (data.dptr == NULL) { + goto done; + } + + SIVAL(data.dptr, 0, dom_seqnum); + SBVAL(data.dptr, 4, timeout); + memcpy(data.dptr + 12, resp->data, resp->length); + + tdb_store(wcache->tdb, key, data, 0); + +done: + TALLOC_FREE(key.dptr); + return; +} diff --git a/source3/winbindd/winbindd_ccache_access.c b/source3/winbindd/winbindd_ccache_access.c new file mode 100644 index 0000000..cc395ad --- /dev/null +++ b/source3/winbindd/winbindd_ccache_access.c @@ -0,0 +1,397 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - cached credentials functions + + Copyright (C) Robert O'Callahan 2006 + Copyright (C) Jeremy Allison 2006 (minor fixes to fit into Samba and + protect against integer wrap). + Copyright (C) Andrew Bartlett 2011 + + 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.h" +#include "auth/gensec/gensec.h" +#include "auth_generic.h" +#include "lib/util/string_wrappers.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +static bool client_can_access_ccache_entry(uid_t client_uid, + struct WINBINDD_MEMORY_CREDS *entry) +{ + if (client_uid == entry->uid || client_uid == 0) { + DEBUG(10, ("Access granted to uid %u\n", (unsigned int)client_uid)); + return True; + } + + DEBUG(1, ("Access denied to uid %u (expected %u)\n", + (unsigned int)client_uid, (unsigned int)entry->uid)); + return False; +} + +static NTSTATUS do_ntlm_auth_with_stored_pw(const char *namespace, + const char *domain, + const char *username, + const char *password, + const DATA_BLOB initial_msg, + const DATA_BLOB challenge_msg, + TALLOC_CTX *mem_ctx, + DATA_BLOB *auth_msg, + uint8_t session_key[16], + uint8_t *new_spnego) +{ + NTSTATUS status; + struct auth_generic_state *auth_generic_state = NULL; + DATA_BLOB reply, session_key_blob; + + status = auth_generic_client_prepare(mem_ctx, &auth_generic_state); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Could not start NTLMSSP client: %s\n", + nt_errstr(status))); + goto done; + } + + status = auth_generic_set_username(auth_generic_state, username); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Could not set username: %s\n", + nt_errstr(status))); + goto done; + } + + status = auth_generic_set_domain(auth_generic_state, domain); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Could not set domain: %s\n", + nt_errstr(status))); + goto done; + } + + status = auth_generic_set_password(auth_generic_state, password); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Could not set password: %s\n", + nt_errstr(status))); + goto done; + } + + if (initial_msg.length == 0) { + gensec_want_feature(auth_generic_state->gensec_security, + GENSEC_FEATURE_SESSION_KEY); + } + + status = auth_generic_client_start_by_name(auth_generic_state, + "ntlmssp_resume_ccache"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Could not start NTLMSSP resume mech: %s\n", + nt_errstr(status))); + goto done; + } + + /* + * We inject the initial NEGOTIATE message our caller used + * in order to get the state machine into the correct position. + */ + reply = data_blob_null; + status = gensec_update(auth_generic_state->gensec_security, + talloc_tos(), initial_msg, &reply); + data_blob_free(&reply); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + DEBUG(1, ("Failed to create initial message! [%s]\n", + nt_errstr(status))); + goto done; + } + + /* Now we are ready to handle the server's actual response. */ + status = gensec_update(auth_generic_state->gensec_security, + mem_ctx, challenge_msg, &reply); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + DEBUG(1, ("We didn't get a response to the challenge! [%s]\n", + nt_errstr(status))); + data_blob_free(&reply); + goto done; + } + + status = gensec_session_key(auth_generic_state->gensec_security, + talloc_tos(), &session_key_blob); + if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) { + DEBUG(1, ("We didn't get the session key we requested! [%s]\n", + nt_errstr(status))); + data_blob_free(&reply); + goto done; + } + + if (session_key_blob.length != 16) { + DEBUG(1, ("invalid session key length %d\n", + (int)session_key_blob.length)); + data_blob_free(&reply); + goto done; + } + memcpy(session_key, session_key_blob.data, 16); + data_blob_free(&session_key_blob); + *auth_msg = reply; + *new_spnego = gensec_have_feature(auth_generic_state->gensec_security, + GENSEC_FEATURE_NEW_SPNEGO); + status = NT_STATUS_OK; + +done: + TALLOC_FREE(auth_generic_state); + return status; +} + +static bool check_client_uid(struct winbindd_cli_state *state, uid_t uid) +{ + int ret; + uid_t ret_uid; + gid_t ret_gid; + + ret_uid = (uid_t)-1; + + ret = getpeereid(state->sock, &ret_uid, &ret_gid); + if (ret != 0) { + DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; " + "denying access\n", strerror(errno))); + return False; + } + + if (uid != ret_uid && ret_uid != sec_initial_uid()) { + DEBUG(1, ("check_client_uid: Client lied about its uid: said %u, " + "actually was %u; denying access\n", + (unsigned int)uid, (unsigned int)ret_uid)); + return False; + } + + return True; +} + +bool winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain; + char *name_namespace = NULL; + char *name_domain = NULL; + char *name_user = NULL; + char *auth_user = NULL; + NTSTATUS result = NT_STATUS_NOT_SUPPORTED; + struct WINBINDD_MEMORY_CREDS *entry; + DATA_BLOB initial, challenge, auth; + uint32_t initial_blob_len, challenge_blob_len, extra_len; + bool ok; + + /* Ensure null termination */ + state->request->data.ccache_ntlm_auth.user[ + sizeof(state->request->data.ccache_ntlm_auth.user)-1]='\0'; + + DEBUG(3, ("[%5lu]: perform NTLM auth on behalf of user %s\n", (unsigned long)state->pid, + state->request->data.ccache_ntlm_auth.user)); + + /* Parse domain and username */ + + auth_user = state->request->data.ccache_ntlm_auth.user; + ok = canonicalize_username(state, + &auth_user, + &name_namespace, + &name_domain, + &name_user); + if (!ok) { + DEBUG(5,("winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s]\n", + state->request->data.ccache_ntlm_auth.user)); + return false; + } + + fstrcpy(state->request->data.ccache_ntlm_auth.user, auth_user); + TALLOC_FREE(auth_user); + + domain = find_auth_domain(state->request->flags, name_domain); + + if (domain == NULL) { + DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n", + name_domain)); + return false; + } + + if (!check_client_uid(state, state->request->data.ccache_ntlm_auth.uid)) { + return false; + } + + /* validate blob lengths */ + initial_blob_len = state->request->data.ccache_ntlm_auth.initial_blob_len; + challenge_blob_len = state->request->data.ccache_ntlm_auth.challenge_blob_len; + extra_len = state->request->extra_len; + + if (initial_blob_len > extra_len || challenge_blob_len > extra_len || + initial_blob_len + challenge_blob_len > extra_len || + initial_blob_len + challenge_blob_len < initial_blob_len || + initial_blob_len + challenge_blob_len < challenge_blob_len) { + + DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun " + "or wrap. Buffer [%d+%d > %d]\n", + initial_blob_len, + challenge_blob_len, + extra_len)); + goto process_result; + } + + TALLOC_FREE(name_namespace); + TALLOC_FREE(name_domain); + TALLOC_FREE(name_user); + /* Parse domain and username */ + ok = parse_domain_user(state, + state->request->data.ccache_ntlm_auth.user, + &name_namespace, + &name_domain, + &name_user); + if (!ok) { + DEBUG(10,("winbindd_dual_ccache_ntlm_auth: cannot parse " + "domain and user from name [%s]\n", + state->request->data.ccache_ntlm_auth.user)); + goto process_result; + } + + entry = find_memory_creds_by_name(state->request->data.ccache_ntlm_auth.user); + if (entry == NULL || entry->nt_hash == NULL || entry->lm_hash == NULL) { + DEBUG(10,("winbindd_dual_ccache_ntlm_auth: could not find " + "credentials for user %s\n", + state->request->data.ccache_ntlm_auth.user)); + goto process_result; + } + + DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry->username)); + + if (!client_can_access_ccache_entry(state->request->data.ccache_ntlm_auth.uid, entry)) { + goto process_result; + } + + if (initial_blob_len == 0 && challenge_blob_len == 0) { + /* this is just a probe to see if credentials are available. */ + result = NT_STATUS_OK; + state->response->data.ccache_ntlm_auth.auth_blob_len = 0; + goto process_result; + } + + initial = data_blob_const(state->request->extra_data.data, + initial_blob_len); + challenge = data_blob_const( + state->request->extra_data.data + initial_blob_len, + state->request->data.ccache_ntlm_auth.challenge_blob_len); + + result = do_ntlm_auth_with_stored_pw( + name_namespace, + name_domain, + name_user, + entry->pass, + initial, + challenge, + talloc_tos(), + &auth, + state->response->data.ccache_ntlm_auth.session_key, + &state->response->data.ccache_ntlm_auth.new_spnego); + + if (!NT_STATUS_IS_OK(result)) { + goto process_result; + } + + state->response->extra_data.data = talloc_memdup( + state->mem_ctx, auth.data, auth.length); + if (!state->response->extra_data.data) { + result = NT_STATUS_NO_MEMORY; + goto process_result; + } + state->response->length += auth.length; + state->response->data.ccache_ntlm_auth.auth_blob_len = auth.length; + + data_blob_free(&auth); + + process_result: + TALLOC_FREE(name_namespace); + TALLOC_FREE(name_domain); + TALLOC_FREE(name_user); + return NT_STATUS_IS_OK(result); +} + +bool winbindd_ccache_save(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain; + char *name_namespace = NULL; + char *name_domain = NULL; + char *name_user = NULL; + char *save_user = NULL; + NTSTATUS status; + bool ok; + + /* Ensure null termination */ + state->request->data.ccache_save.user[ + sizeof(state->request->data.ccache_save.user)-1]='\0'; + state->request->data.ccache_save.pass[ + sizeof(state->request->data.ccache_save.pass)-1]='\0'; + + DEBUG(3, ("[%5lu]: save password of user %s\n", + (unsigned long)state->pid, + state->request->data.ccache_save.user)); + + /* Parse domain and username */ + + + save_user = state->request->data.ccache_save.user; + ok = canonicalize_username(state, + &save_user, + &name_namespace, + &name_domain, + &name_user); + if (!ok) { + DEBUG(5,("winbindd_ccache_save: cannot parse domain and user " + "from name [%s]\n", + state->request->data.ccache_save.user)); + return false; + } + + fstrcpy(state->request->data.ccache_save.user, save_user); + + /* + * The domain is checked here only for compatibility + * reasons. We used to do the winbindd memory ccache for + * ntlm_auth in the domain child. With that code, we had to + * make sure that we do have a domain around to send this + * to. Now we do the memory cache in the parent winbindd, + * where it would not matter if we have a domain or not. + */ + + domain = find_auth_domain(state->request->flags, name_domain); + if (domain == NULL) { + DEBUG(5, ("winbindd_ccache_save: can't get domain [%s]\n", + name_domain)); + return false; + } + + if (!check_client_uid(state, state->request->data.ccache_save.uid)) { + return false; + } + + status = winbindd_add_memory_creds( + state->request->data.ccache_save.user, + state->request->data.ccache_save.uid, + state->request->data.ccache_save.pass); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("winbindd_add_memory_creds failed %s\n", + nt_errstr(status))); + return false; + } + return true; +} diff --git a/source3/winbindd/winbindd_change_machine_acct.c b/source3/winbindd/winbindd_change_machine_acct.c new file mode 100644 index 0000000..fe5b9bf --- /dev/null +++ b/source3/winbindd/winbindd_change_machine_acct.c @@ -0,0 +1,99 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_CHANGE_MACHINE_ACCT + Copyright (C) Volker Lendecke 2009 + Copyright (C) Guenther Deschner 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_change_machine_acct_state { + uint8_t dummy; +}; + +static void winbindd_change_machine_acct_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_change_machine_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_change_machine_acct_state *state; + struct winbindd_domain *domain; + const char *dcname = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_change_machine_acct_state); + if (req == NULL) { + return NULL; + } + + if (request->data.init_conn.dcname[0] != '\0') { + dcname = request->data.init_conn.dcname; + } + + domain = find_domain_from_name(request->domain_name); + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + if (domain->internal) { + /* + * Internal domains are passdb based, we can always + * contact them. + * + * This also protects us from changing the password on + * the AD DC without updating all the right databases. + * Do not remove this until that code is fixed. + */ + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_ChangeMachineAccount_send(state, ev, + dom_child_handle(domain), + dcname); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_change_machine_acct_done, req); + return req; +} + +static void winbindd_change_machine_acct_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_change_machine_acct_state *state = tevent_req_data( + req, struct winbindd_change_machine_acct_state); + NTSTATUS status, result; + + status = dcerpc_wbint_ChangeMachineAccount_recv(subreq, state, &result); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_change_machine_acct_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + return tevent_req_simple_recv_ntstatus(req); +} diff --git a/source3/winbindd/winbindd_check_machine_acct.c b/source3/winbindd/winbindd_check_machine_acct.c new file mode 100644 index 0000000..c657374 --- /dev/null +++ b/source3/winbindd/winbindd_check_machine_acct.c @@ -0,0 +1,96 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_CHECK_MACHINE_ACCT + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_check_machine_acct_state { + uint8_t dummy; +}; + +static void winbindd_check_machine_acct_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_check_machine_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_check_machine_acct_state *state; + struct winbindd_domain *domain; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_check_machine_acct_state); + if (req == NULL) { + return NULL; + } + + if (request->domain_name[0] == '\0') { + /* preserve old behavior, when no domain name is given */ + domain = find_our_domain(); + } else { + domain = find_domain_from_name(request->domain_name); + } + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + if (domain->internal) { + /* + * Internal domains are passdb based, we can always + * contact them. + */ + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_CheckMachineAccount_send(state, ev, + dom_child_handle(domain)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_check_machine_acct_done, req); + return req; +} + +static void winbindd_check_machine_acct_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_check_machine_acct_state *state = tevent_req_data( + req, struct winbindd_check_machine_acct_state); + NTSTATUS status, result; + + status = dcerpc_wbint_CheckMachineAccount_recv(subreq, state, &result); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_check_machine_acct_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + NTSTATUS status = tevent_req_simple_recv_ntstatus(req); + + set_auth_errors(presp, status); + return status; +} diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c new file mode 100644 index 0000000..1685edb --- /dev/null +++ b/source3/winbindd/winbindd_cm.c @@ -0,0 +1,3434 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon connection manager + + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett 2002 + Copyright (C) Gerald (Jerry) Carter 2003-2005. + Copyright (C) Volker Lendecke 2004-2005 + Copyright (C) Jeremy Allison 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + We need to manage connections to domain controllers without having to + mess up the main winbindd code with other issues. The aim of the + connection manager is to: + + - make connections to domain controllers and cache them + - re-establish connections when networks or servers go down + - centralise the policy on connection timeouts, domain controller + selection etc + - manage re-entrancy for when winbindd becomes able to handle + multiple outstanding rpc requests + + Why not have connection management as part of the rpc layer like tng? + Good question. This code may morph into libsmb/rpc_cache.c or something + like that but at the moment it's simply staying as part of winbind. I + think the TNG architecture of forcing every user of the rpc layer to use + the connection caching system is a bad idea. It should be an optional + method of using the routines. + + The TNG design is quite good but I disagree with some aspects of the + implementation. -tpot + + */ + +/* + TODO: + + - I'm pretty annoyed by all the make_nmb_name() stuff. It should be + moved down into another function. + + - Take care when destroying cli_structs as they can be shared between + various sam handles. + + */ + +#include "includes.h" +#include "winbindd.h" +#include "libsmb/namequery.h" +#include "../libcli/auth/libcli_auth.h" +#include "../librpc/gen_ndr/ndr_netlogon_c.h" +#include "rpc_client/cli_pipe.h" +#include "rpc_client/cli_netlogon.h" +#include "../librpc/gen_ndr/ndr_samr_c.h" +#include "../librpc/gen_ndr/ndr_lsa_c.h" +#include "rpc_client/cli_lsarpc.h" +#include "../librpc/gen_ndr/ndr_dssetup_c.h" +#include "libads/sitename_cache.h" +#include "libsmb/libsmb.h" +#include "libsmb/clidgram.h" +#include "ads.h" +#include "secrets.h" +#include "../libcli/security/security.h" +#include "passdb.h" +#include "messages.h" +#include "auth/gensec/gensec.h" +#include "../libcli/smb/smbXcli_base.h" +#include "libcli/auth/netlogon_creds_cli.h" +#include "auth.h" +#include "rpc_server/rpc_ncacn_np.h" +#include "auth/credentials/credentials.h" +#include "lib/param/param.h" +#include "lib/gencache.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +struct dc_name_ip { + fstring name; + struct sockaddr_storage ss; +}; + +extern struct winbindd_methods reconnect_methods; + +static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain, bool need_rw_dc); +static void set_dc_type_and_flags( struct winbindd_domain *domain ); +static bool set_dc_type_and_flags_trustinfo( struct winbindd_domain *domain ); +static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, + struct dc_name_ip **dcs, int *num_dcs, + uint32_t request_flags); + +void winbind_msg_domain_offline(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + const char *domain_name = (const char *)data->data; + struct winbindd_domain *domain; + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + DBG_DEBUG("Domain %s not found!\n", domain_name); + return; + } + + DBG_DEBUG("Domain %s was %s, change to offline now.\n", + domain_name, + domain->online ? "online" : "offline"); + + domain->online = false; +} + +void winbind_msg_domain_online(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + const char *domain_name = (const char *)data->data; + struct winbindd_domain *domain; + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + return; + } + + SMB_ASSERT(wb_child_domain() == NULL); + + DBG_DEBUG("Domain %s was %s, marking as online now!\n", + domain_name, + domain->online ? "online" : "offline"); + + domain->online = true; +} + +/**************************************************************** + Set domain offline and also add handler to put us back online + if we detect a DC. +****************************************************************/ + +void set_domain_offline(struct winbindd_domain *domain) +{ + pid_t parent_pid = getppid(); + + DEBUG(10,("set_domain_offline: called for domain %s\n", + domain->name )); + + if (domain->internal) { + DEBUG(3,("set_domain_offline: domain %s is internal - logic error.\n", + domain->name )); + return; + } + + domain->online = False; + + /* Offline domains are always initialized. They're + re-initialized when they go back online. */ + + domain->initialized = True; + + /* Send a message to the parent that the domain is offline. */ + if (parent_pid > 1 && !domain->internal) { + messaging_send_buf(global_messaging_context(), + pid_to_procid(parent_pid), + MSG_WINBIND_DOMAIN_OFFLINE, + (uint8_t *)domain->name, + strlen(domain->name) + 1); + } + + /* Send an offline message to the idmap child when our + primary domain goes offline */ + if ( domain->primary ) { + pid_t idmap_pid = idmap_child_pid(); + + if (idmap_pid != 0) { + messaging_send_buf(global_messaging_context(), + pid_to_procid(idmap_pid), + MSG_WINBIND_OFFLINE, + (const uint8_t *)domain->name, + strlen(domain->name)+1); + } + } + + return; +} + +/**************************************************************** + Set domain online - if allowed. +****************************************************************/ + +static void set_domain_online(struct winbindd_domain *domain) +{ + pid_t parent_pid = getppid(); + + DEBUG(10,("set_domain_online: called for domain %s\n", + domain->name )); + + if (domain->internal) { + DEBUG(3,("set_domain_online: domain %s is internal - logic error.\n", + domain->name )); + return; + } + + if (get_global_winbindd_state_offline()) { + DEBUG(10,("set_domain_online: domain %s remaining globally offline\n", + domain->name )); + return; + } + + winbindd_set_locator_kdc_envs(domain); + + /* If we are waiting to get a krb5 ticket, trigger immediately. */ + ccache_regain_all_now(); + + /* Ok, we're out of any startup mode now... */ + domain->startup = False; + + if (domain->online == False) { + /* We were offline - now we're online. We default to + using the MS-RPC backend if we started offline, + and if we're going online for the first time we + should really re-initialize the backends and the + checks to see if we're talking to an AD or NT domain. + */ + + domain->initialized = False; + + /* 'reconnect_methods' is the MS-RPC backend. */ + if (domain->backend == &reconnect_methods) { + domain->backend = NULL; + } + } + + domain->online = True; + + /* Send a message to the parent that the domain is online. */ + if (parent_pid > 1 && !domain->internal) { + messaging_send_buf(global_messaging_context(), + pid_to_procid(parent_pid), + MSG_WINBIND_DOMAIN_ONLINE, + (uint8_t *)domain->name, + strlen(domain->name) + 1); + } + + /* Send an online message to the idmap child when our + primary domain comes online */ + + if ( domain->primary ) { + pid_t idmap_pid = idmap_child_pid(); + + if (idmap_pid != 0) { + messaging_send_buf(global_messaging_context(), + pid_to_procid(idmap_pid), + MSG_WINBIND_ONLINE, + (const uint8_t *)domain->name, + strlen(domain->name)+1); + } + } + + return; +} + +/**************************************************************** + Requested to set a domain online. +****************************************************************/ + +void set_domain_online_request(struct winbindd_domain *domain) +{ + NTSTATUS status; + + SMB_ASSERT(wb_child_domain() || idmap_child()); + + DEBUG(10,("set_domain_online_request: called for domain %s\n", + domain->name )); + + if (get_global_winbindd_state_offline()) { + DEBUG(10,("set_domain_online_request: domain %s remaining globally offline\n", + domain->name )); + return; + } + + if (domain->internal) { + DEBUG(10, ("set_domain_online_request: Internal domains are " + "always online\n")); + return; + } + + /* + * This call takes care of setting the online flag to true if we + * connected, or tell the parent to ping us back if false. Bypasses + * online check so always does network calls. + */ + status = init_dc_connection_network(domain, true); + DBG_DEBUG("init_dc_connection_network(), returned %s, called for " + "domain %s (online = %s)\n", + nt_errstr(status), + domain->name, + domain->online ? "true" : "false"); +} + +/**************************************************************** + Add -ve connection cache entries for domain and realm. +****************************************************************/ + +static void winbind_add_failed_connection_entry( + const struct winbindd_domain *domain, + const char *server, + NTSTATUS result) +{ + add_failed_connection_entry(domain->name, server, result); + /* If this was the saf name for the last thing we talked to, + remove it. */ + saf_delete(domain->name); + if (domain->alt_name != NULL) { + add_failed_connection_entry(domain->alt_name, server, result); + saf_delete(domain->alt_name); + } + winbindd_unset_locator_kdc_env(domain); +} + +/* Choose between anonymous or authenticated connections. We need to use + an authenticated connection if DCs have the RestrictAnonymous registry + entry set > 0, or the "Additional restrictions for anonymous + connections" set in the win2k Local Security Policy. + + Caller to free() result in domain, username, password +*/ + +static void cm_get_ipc_userpass(char **username, char **domain, char **password) +{ + *username = (char *)secrets_fetch(SECRETS_AUTH_USER, NULL); + *domain = (char *)secrets_fetch(SECRETS_AUTH_DOMAIN, NULL); + *password = (char *)secrets_fetch(SECRETS_AUTH_PASSWORD, NULL); + + if (*username && **username) { + + if (!*domain || !**domain) + *domain = smb_xstrdup(lp_workgroup()); + + if (!*password || !**password) + *password = smb_xstrdup(""); + + DEBUG(3, ("cm_get_ipc_userpass: Retrieved auth-user from secrets.tdb [%s\\%s]\n", + *domain, *username)); + + } else { + DEBUG(3, ("cm_get_ipc_userpass: No auth-user defined\n")); + *username = smb_xstrdup(""); + *domain = smb_xstrdup(""); + *password = smb_xstrdup(""); + } +} + +static NTSTATUS cm_get_ipc_credentials(TALLOC_CTX *mem_ctx, + struct cli_credentials **_creds) +{ + + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status = NT_STATUS_INTERNAL_ERROR; + struct loadparm_context *lp_ctx; + char *username = NULL; + char *netbios_domain = NULL; + char *password = NULL; + struct cli_credentials *creds = NULL; + bool ok; + + cm_get_ipc_userpass(&username, &netbios_domain, &password); + + lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers()); + if (lp_ctx == NULL) { + DEBUG(1, ("loadparm_init_s3 failed\n")); + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + creds = cli_credentials_init(mem_ctx); + if (creds == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + ok = cli_credentials_set_conf(creds, lp_ctx); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + + cli_credentials_set_kerberos_state(creds, + CRED_USE_KERBEROS_DISABLED, + CRED_SPECIFIED); + + ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + ok = cli_credentials_set_username(creds, username, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + ok = cli_credentials_set_password(creds, password, CRED_SPECIFIED); + if (!ok) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + *_creds = creds; + creds = NULL; + status = NT_STATUS_OK; + fail: + TALLOC_FREE(creds); + SAFE_FREE(username); + SAFE_FREE(netbios_domain); + SAFE_FREE(password); + TALLOC_FREE(frame); + return status; +} + +static bool cm_is_ipc_credentials(struct cli_credentials *creds) +{ + TALLOC_CTX *frame = talloc_stackframe(); + char *ipc_account = NULL; + char *ipc_domain = NULL; + char *ipc_password = NULL; + const char *creds_account = NULL; + const char *creds_domain = NULL; + const char *creds_password = NULL; + bool ret = false; + + cm_get_ipc_userpass(&ipc_account, &ipc_domain, &ipc_password); + + creds_account = cli_credentials_get_username(creds); + creds_domain = cli_credentials_get_domain(creds); + creds_password = cli_credentials_get_password(creds); + + if (!strequal(ipc_domain, creds_domain)) { + goto done; + } + + if (!strequal(ipc_account, creds_account)) { + goto done; + } + + if (!strcsequal(ipc_password, creds_password)) { + goto done; + } + + ret = true; + done: + SAFE_FREE(ipc_account); + SAFE_FREE(ipc_domain); + SAFE_FREE(ipc_password); + TALLOC_FREE(frame); + return ret; +} + +static bool get_dc_name_via_netlogon(struct winbindd_domain *domain, + fstring dcname, + struct sockaddr_storage *dc_ss, + uint32_t request_flags) +{ + struct winbindd_domain *our_domain = NULL; + struct rpc_pipe_client *netlogon_pipe = NULL; + NTSTATUS result; + WERROR werr; + TALLOC_CTX *mem_ctx; + unsigned int orig_timeout; + const char *tmp = NULL; + const char *p; + struct dcerpc_binding_handle *b; + + /* Hmmmm. We can only open one connection to the NETLOGON pipe at the + * moment.... */ + + if (IS_DC) { + return False; + } + + if (domain->primary) { + return False; + } + + our_domain = find_our_domain(); + + if ((mem_ctx = talloc_init("get_dc_name_via_netlogon")) == NULL) { + return False; + } + + result = cm_connect_netlogon(our_domain, &netlogon_pipe); + if (!NT_STATUS_IS_OK(result)) { + talloc_destroy(mem_ctx); + return False; + } + + b = netlogon_pipe->binding_handle; + + /* This call can take a long time - allow the server to time out. + 35 seconds should do it. */ + + orig_timeout = rpccli_set_timeout(netlogon_pipe, 35000); + + if (our_domain->active_directory) { + struct netr_DsRGetDCNameInfo *domain_info = NULL; + + /* + * TODO request flags are not respected in the server + * (and in some cases, like REQUIRE_PDC, causes an error) + */ + result = dcerpc_netr_DsRGetDCName(b, + mem_ctx, + our_domain->dcname, + domain->name, + NULL, + NULL, + request_flags|DS_RETURN_DNS_NAME, + &domain_info, + &werr); + if (NT_STATUS_IS_OK(result) && W_ERROR_IS_OK(werr)) { + tmp = talloc_strdup( + mem_ctx, domain_info->dc_unc); + if (tmp == NULL) { + DEBUG(0, ("talloc_strdup failed\n")); + talloc_destroy(mem_ctx); + return false; + } + if (domain->alt_name == NULL) { + domain->alt_name = talloc_strdup(domain, + domain_info->domain_name); + if (domain->alt_name == NULL) { + DEBUG(0, ("talloc_strdup failed\n")); + talloc_destroy(mem_ctx); + return false; + } + } + if (domain->forest_name == NULL) { + domain->forest_name = talloc_strdup(domain, + domain_info->forest_name); + if (domain->forest_name == NULL) { + DEBUG(0, ("talloc_strdup failed\n")); + talloc_destroy(mem_ctx); + return false; + } + } + } + } else { + result = dcerpc_netr_GetAnyDCName(b, mem_ctx, + our_domain->dcname, + domain->name, + &tmp, + &werr); + } + + /* And restore our original timeout. */ + rpccli_set_timeout(netlogon_pipe, orig_timeout); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("dcerpc_netr_GetAnyDCName failed: %s\n", + nt_errstr(result))); + talloc_destroy(mem_ctx); + return false; + } + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(10,("dcerpc_netr_GetAnyDCName failed: %s\n", + win_errstr(werr))); + talloc_destroy(mem_ctx); + return false; + } + + /* dcerpc_netr_GetAnyDCName gives us a name with \\ */ + p = strip_hostname(tmp); + + fstrcpy(dcname, p); + + talloc_destroy(mem_ctx); + + DEBUG(10,("dcerpc_netr_GetAnyDCName returned %s\n", dcname)); + + if (!resolve_name(dcname, dc_ss, 0x20, true)) { + return False; + } + + return True; +} + +/** + * Helper function to assemble trust password and account name + */ +static NTSTATUS get_trust_credentials(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + bool netlogon, + struct cli_credentials **_creds) +{ + const struct winbindd_domain *creds_domain = NULL; + struct cli_credentials *creds; + NTSTATUS status; + bool force_machine_account = false; + + /* If we are a DC and this is not our own domain */ + + if (!domain->active_directory) { + if (!netlogon) { + /* + * For non active directory domains + * we can only use NTLMSSP for SMB. + * + * But the trust account is not allowed + * to use SMB with NTLMSSP. + */ + force_machine_account = true; + } + } + + if (IS_DC && !force_machine_account) { + creds_domain = domain; + } else { + creds_domain = find_our_domain(); + if (creds_domain == NULL) { + return NT_STATUS_INVALID_SERVER_STATE; + } + } + + status = pdb_get_trust_credentials(creds_domain->name, + creds_domain->alt_name, + mem_ctx, + &creds); + if (!NT_STATUS_IS_OK(status)) { + goto ipc_fallback; + } + + if (creds_domain != domain) { + /* + * We can only use schannel against a direct trust + */ + cli_credentials_set_secure_channel_type(creds, + SEC_CHAN_NULL); + } + + *_creds = creds; + return NT_STATUS_OK; + + ipc_fallback: + if (netlogon) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + status = cm_get_ipc_credentials(mem_ctx, &creds); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *_creds = creds; + return NT_STATUS_OK; +} + +/************************************************************************ + Given a fd with a just-connected TCP connection to a DC, open a connection + to the pipe. +************************************************************************/ + +static NTSTATUS cm_prepare_connection(struct winbindd_domain *domain, + const int sockfd, + const char *controller, + struct cli_state **cli, + bool *retry) +{ + bool try_ipc_auth = false; + const char *machine_principal = NULL; + const char *machine_realm = NULL; + const char *machine_account = NULL; + const char *machine_domain = NULL; + int flags = 0; + struct cli_credentials *creds = NULL; + + struct named_mutex *mutex; + + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + NTSTATUS tmp_status; + NTSTATUS tcon_status = NT_STATUS_NETWORK_NAME_DELETED; + + enum smb_signing_setting smb_sign_client_connections = lp_client_ipc_signing(); + + if (IS_DC) { + if (domain->secure_channel_type == SEC_CHAN_NULL) { + /* + * Make sure we don't even try to + * connect to a foreign domain + * without a direct outbound trust. + */ + close(sockfd); + return NT_STATUS_NO_TRUST_LSA_SECRET; + } + + /* + * As AD DC we only use netlogon and lsa + * using schannel over an anonymous transport + * (ncacn_ip_tcp or ncacn_np). + * + * Currently we always establish the SMB connection, + * even if we don't use it, because we later use ncacn_ip_tcp. + * + * As we won't use the SMB connection there's no + * need to try kerberos. And NT4 domains expect + * an anonymous IPC$ connection anyway. + */ + smb_sign_client_connections = SMB_SIGNING_OFF; + } + + if (smb_sign_client_connections == SMB_SIGNING_DEFAULT) { + /* + * If we are connecting to our own AD domain, require + * smb signing to disrupt MITM attacks + */ + if (domain->primary && lp_security() == SEC_ADS) { + smb_sign_client_connections = SMB_SIGNING_REQUIRED; + /* + * If we are in or are an AD domain and connecting to another + * AD domain in our forest + * then require smb signing to disrupt MITM attacks + */ + } else if ((lp_security() == SEC_ADS) + && domain->active_directory + && (domain->domain_trust_attribs + & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST)) { + smb_sign_client_connections = SMB_SIGNING_REQUIRED; + } + } + + DEBUG(10,("cm_prepare_connection: connecting to DC %s for domain %s\n", + controller, domain->name )); + + *retry = True; + + mutex = grab_named_mutex(talloc_tos(), controller, + WINBIND_SERVER_MUTEX_WAIT_TIME); + if (mutex == NULL) { + close(sockfd); + DEBUG(0,("cm_prepare_connection: mutex grab failed for %s\n", + controller)); + result = NT_STATUS_POSSIBLE_DEADLOCK; + goto done; + } + + /* + * cm_prepare_connection() is responsible that sockfd does not leak. + * Once cli_state_create() returns with success, the + * smbXcli_conn_destructor() makes sure that close(sockfd) is finally + * called. Till that, close(sockfd) must be called on every unsuccessful + * return. + */ + *cli = cli_state_create(NULL, sockfd, controller, + smb_sign_client_connections, flags); + if (*cli == NULL) { + close(sockfd); + DEBUG(1, ("Could not cli_initialize\n")); + result = NT_STATUS_NO_MEMORY; + goto done; + } + + cli_set_timeout(*cli, 10000); /* 10 seconds */ + + set_socket_options(sockfd, lp_socket_options()); + + result = smbXcli_negprot((*cli)->conn, + (*cli)->timeout, + lp_client_ipc_min_protocol(), + lp_client_ipc_max_protocol(), + NULL, + NULL, + NULL); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("cli_negprot failed: %s\n", nt_errstr(result))); + goto done; + } + + if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_NT1 && + smb1cli_conn_capabilities((*cli)->conn) & CAP_EXTENDED_SECURITY) { + try_ipc_auth = true; + } else if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_SMB2_02) { + try_ipc_auth = true; + } else if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) { + /* + * If we are forcing on SMB signing, then we must + * require authentication unless this is a one-way + * trust, and we have no stored user/password + */ + try_ipc_auth = true; + } + + if (IS_DC) { + /* + * As AD DC we only use netlogon and lsa + * using schannel over an anonymous transport + * (ncacn_ip_tcp or ncacn_np). + * + * Currently we always establish the SMB connection, + * even if we don't use it, because we later use ncacn_ip_tcp. + * + * As we won't use the SMB connection there's no + * need to try kerberos. And NT4 domains expect + * an anonymous IPC$ connection anyway. + */ + try_ipc_auth = false; + } + + if (try_ipc_auth) { + result = get_trust_credentials(domain, talloc_tos(), false, &creds); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("get_trust_credentials(%s) failed: %s\n", + domain->name, nt_errstr(result))); + goto done; + } + } else { + /* + * Without SPNEGO or NTLMSSP (perhaps via SMB2) we + * would try and authentication with our machine + * account password and fail. This is very rare in + * the modern world however + */ + creds = cli_credentials_init_anon(talloc_tos()); + if (creds == NULL) { + result = NT_STATUS_NO_MEMORY; + DEBUG(1, ("cli_credentials_init_anon(%s) failed: %s\n", + domain->name, nt_errstr(result))); + goto done; + } + } + + machine_principal = cli_credentials_get_principal(creds, + talloc_tos()); + machine_realm = cli_credentials_get_realm(creds); + machine_account = cli_credentials_get_username(creds); + machine_domain = cli_credentials_get_domain(creds); + + DEBUG(5, ("connecting to %s (%s, %s) with account [%s\\%s] principal " + "[%s] and realm [%s]\n", + controller, domain->name, domain->alt_name, + machine_domain, machine_account, + machine_principal, machine_realm)); + + if (cli_credentials_is_anonymous(creds)) { + goto anon_fallback; + } + + winbindd_set_locator_kdc_envs(domain); + + result = cli_session_setup_creds(*cli, creds); + if (NT_STATUS_IS_OK(result)) { + goto session_setup_done; + } + + DEBUG(1, ("authenticated session setup to %s using %s failed with %s\n", + controller, + cli_credentials_get_unparsed_name(creds, talloc_tos()), + nt_errstr(result))); + + /* + * If we are not going to validate the connection + * with SMB signing, then allow us to fall back to + * anonymous + */ + if (NT_STATUS_EQUAL(result, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT) + || NT_STATUS_EQUAL(result, NT_STATUS_TRUSTED_DOMAIN_FAILURE) + || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_ACCOUNT_NAME) + || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_COMPUTER_NAME) + || NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_DOMAIN) + || NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) + || NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE)) + { + if (!cm_is_ipc_credentials(creds)) { + goto ipc_fallback; + } + + if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) { + goto done; + } + + goto anon_fallback; + } + + goto done; + + ipc_fallback: + TALLOC_FREE(creds); + tmp_status = cm_get_ipc_credentials(talloc_tos(), &creds); + if (!NT_STATUS_IS_OK(tmp_status)) { + result = tmp_status; + goto done; + } + + if (cli_credentials_is_anonymous(creds)) { + goto anon_fallback; + } + + machine_account = cli_credentials_get_username(creds); + machine_domain = cli_credentials_get_domain(creds); + + DEBUG(5, ("connecting to %s from %s using NTLMSSP with username " + "[%s]\\[%s]\n", controller, lp_netbios_name(), + machine_domain, machine_account)); + + result = cli_session_setup_creds(*cli, creds); + if (NT_STATUS_IS_OK(result)) { + goto session_setup_done; + } + + DEBUG(1, ("authenticated session setup to %s using %s failed with %s\n", + controller, + cli_credentials_get_unparsed_name(creds, talloc_tos()), + nt_errstr(result))); + + /* + * If we are not going to validate the connection + * with SMB signing, then allow us to fall back to + * anonymous + */ + if (NT_STATUS_EQUAL(result, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT) + || NT_STATUS_EQUAL(result, NT_STATUS_TRUSTED_DOMAIN_FAILURE) + || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_ACCOUNT_NAME) + || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_COMPUTER_NAME) + || NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_DOMAIN) + || NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) + || NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE)) + { + goto anon_fallback; + } + + goto done; + + anon_fallback: + TALLOC_FREE(creds); + + if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) { + goto done; + } + + /* Fall back to anonymous connection, this might fail later */ + DEBUG(5,("cm_prepare_connection: falling back to anonymous " + "connection for DC %s\n", + controller )); + + result = cli_session_setup_anon(*cli); + if (NT_STATUS_IS_OK(result)) { + DEBUG(5, ("Connected anonymously\n")); + goto session_setup_done; + } + + DEBUG(1, ("anonymous session setup to %s failed with %s\n", + controller, nt_errstr(result))); + + /* We can't session setup */ + goto done; + + session_setup_done: + TALLOC_FREE(creds); + + /* + * This should be a short term hack until + * dynamic re-authentication is implemented. + * + * See Bug 9175 - winbindd doesn't recover from + * NT_STATUS_NETWORK_SESSION_EXPIRED + */ + if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_SMB2_02) { + smbXcli_session_set_disconnect_expired((*cli)->smb2.session); + } + + result = cli_tree_connect(*cli, "IPC$", "IPC", NULL); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1,("failed tcon_X with %s\n", nt_errstr(result))); + goto done; + } + tcon_status = result; + + /* cache the server name for later connections */ + + saf_store(domain->name, controller); + if (domain->alt_name) { + saf_store(domain->alt_name, controller); + } + + winbindd_set_locator_kdc_envs(domain); + + TALLOC_FREE(mutex); + *retry = False; + + result = NT_STATUS_OK; + + done: + TALLOC_FREE(mutex); + TALLOC_FREE(creds); + + if (NT_STATUS_IS_OK(result)) { + result = tcon_status; + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("Failed to prepare SMB connection to %s: %s\n", + controller, nt_errstr(result))); + winbind_add_failed_connection_entry(domain, controller, result); + if ((*cli) != NULL) { + cli_shutdown(*cli); + *cli = NULL; + } + } + + return result; +} + +/******************************************************************* + Add a dcname and sockaddr_storage pair to the end of a dc_name_ip + array. + + Keeps the list unique by not adding duplicate entries. + + @param[in] mem_ctx talloc memory context to allocate from + @param[in] domain_name domain of the DC + @param[in] dcname name of the DC to add to the list + @param[in] pss Internet address and port pair to add to the list + @param[in,out] dcs array of dc_name_ip structures to add to + @param[in,out] num_dcs number of dcs returned in the dcs array + @return true if the list was added to, false otherwise +*******************************************************************/ + +static bool add_one_dc_unique(TALLOC_CTX *mem_ctx, const char *domain_name, + const char *dcname, struct sockaddr_storage *pss, + struct dc_name_ip **dcs, int *num) +{ + int i = 0; + + if (!NT_STATUS_IS_OK(check_negative_conn_cache(domain_name, dcname))) { + DEBUG(10, ("DC %s was in the negative conn cache\n", dcname)); + return False; + } + + /* Make sure there's no duplicates in the list */ + for (i=0; i<*num; i++) + if (sockaddr_equal( + (struct sockaddr *)(void *)&(*dcs)[i].ss, + (struct sockaddr *)(void *)pss)) + return False; + + *dcs = talloc_realloc(mem_ctx, *dcs, struct dc_name_ip, (*num)+1); + + if (*dcs == NULL) + return False; + + fstrcpy((*dcs)[*num].name, dcname); + (*dcs)[*num].ss = *pss; + *num += 1; + return True; +} + +static bool add_sockaddr_to_array(TALLOC_CTX *mem_ctx, + struct sockaddr_storage *pss, uint16_t port, + struct sockaddr_storage **addrs, int *num) +{ + *addrs = talloc_realloc(mem_ctx, *addrs, struct sockaddr_storage, (*num)+1); + + if (*addrs == NULL) { + *num = 0; + return False; + } + + (*addrs)[*num] = *pss; + set_sockaddr_port((struct sockaddr *)&(*addrs)[*num], port); + + *num += 1; + return True; +} + +#ifdef HAVE_ADS +static bool dcip_check_name_ads(const struct winbindd_domain *domain, + struct samba_sockaddr *sa, + uint32_t request_flags, + TALLOC_CTX *mem_ctx, + char **namep) +{ + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + char *name = NULL; + ADS_STRUCT *ads = NULL; + ADS_STATUS ads_status; + char addr[INET6_ADDRSTRLEN]; + + print_sockaddr(addr, sizeof(addr), &sa->u.ss); + D_DEBUG("Trying to figure out the DC name for domain '%s' at IP '%s'.\n", + domain->name, + addr); + + ads = ads_init(tmp_ctx, + domain->alt_name, + domain->name, + addr, + ADS_SASL_PLAIN); + if (ads == NULL) { + ads_status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + goto out; + } + ads->auth.flags |= ADS_AUTH_NO_BIND; + ads->config.flags |= request_flags; + ads->server.no_fallback = true; + + ads_status = ads_connect(ads); + if (!ADS_ERR_OK(ads_status)) { + goto out; + } + + /* We got a cldap packet. */ + name = talloc_strdup(tmp_ctx, ads->config.ldap_server_name); + if (name == NULL) { + ads_status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + goto out; + } + namecache_store(name, 0x20, 1, sa); + + DBG_DEBUG("CLDAP flags = 0x%"PRIx32"\n", ads->config.flags); + + if (domain->primary && (ads->config.flags & NBT_SERVER_KDC)) { + if (ads_closest_dc(ads)) { + char *sitename = sitename_fetch(tmp_ctx, + ads->config.realm); + + /* We're going to use this KDC for this realm/domain. + If we are using sites, then force the krb5 libs + to use this KDC. */ + + create_local_private_krb5_conf_for_domain(domain->alt_name, + domain->name, + sitename, + &sa->u.ss); + + TALLOC_FREE(sitename); + } else { + /* use an off site KDC */ + create_local_private_krb5_conf_for_domain(domain->alt_name, + domain->name, + NULL, + &sa->u.ss); + } + winbindd_set_locator_kdc_envs(domain); + + /* Ensure we contact this DC also. */ + saf_store(domain->name, name); + saf_store(domain->alt_name, name); + } + + D_DEBUG("DC name for domain '%s' at IP '%s' is '%s'\n", + domain->name, + addr, + name); + *namep = talloc_move(mem_ctx, &name); + +out: + TALLOC_FREE(tmp_ctx); + + return ADS_ERR_OK(ads_status) ? true : false; +} +#endif + +/******************************************************************* + convert an ip to a name + For an AD Domain, it checks the requirements of the request flags. +*******************************************************************/ + +static bool dcip_check_name(TALLOC_CTX *mem_ctx, + const struct winbindd_domain *domain, + struct sockaddr_storage *pss, + char **name, uint32_t request_flags) +{ + struct samba_sockaddr sa = {0}; + uint32_t nt_version = NETLOGON_NT_VERSION_1; + NTSTATUS status; + const char *dc_name; + fstring nbtname; +#ifdef HAVE_ADS + bool is_ad_domain = false; +#endif + bool ok = sockaddr_storage_to_samba_sockaddr(&sa, pss); + if (!ok) { + return false; + } + +#ifdef HAVE_ADS + /* For active directory servers, try to get the ldap server name. + None of these failures should be considered critical for now */ + + if ((lp_security() == SEC_ADS) && (domain->alt_name != NULL)) { + is_ad_domain = true; + } else if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + is_ad_domain = domain->active_directory; + } + + if (is_ad_domain) { + return dcip_check_name_ads(domain, + &sa, + request_flags, + mem_ctx, + name); + } +#endif + + { + size_t len = strlen(lp_netbios_name()); + char my_acct_name[len+2]; + + snprintf(my_acct_name, + sizeof(my_acct_name), + "%s$", + lp_netbios_name()); + + status = nbt_getdc(global_messaging_context(), 10, &sa.u.ss, + domain->name, &domain->sid, + my_acct_name, ACB_WSTRUST, + nt_version, mem_ctx, &nt_version, + &dc_name, NULL); + } + if (NT_STATUS_IS_OK(status)) { + *name = talloc_strdup(mem_ctx, dc_name); + if (*name == NULL) { + return false; + } + namecache_store(*name, 0x20, 1, &sa); + return True; + } + + /* try node status request */ + + if (name_status_find(domain->name, 0x1c, 0x20, &sa.u.ss, nbtname) ) { + namecache_store(nbtname, 0x20, 1, &sa); + + if (name != NULL) { + *name = talloc_strdup(mem_ctx, nbtname); + if (*name == NULL) { + return false; + } + } + + return true; + } + return False; +} + +/******************************************************************* + Retrieve a list of IP addresses for domain controllers. + + The array is sorted in the preferred connection order. + + @param[in] mem_ctx talloc memory context to allocate from + @param[in] domain domain to retrieve DCs for + @param[out] dcs array of dcs that will be returned + @param[out] num_dcs number of dcs returned in the dcs array + @return always true +*******************************************************************/ + +static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, + struct dc_name_ip **dcs, int *num_dcs, + uint32_t request_flags) +{ + fstring dcname; + struct sockaddr_storage ss; + struct samba_sockaddr *sa_list = NULL; + size_t salist_size = 0; + size_t i; + bool is_our_domain; + enum security_types sec = (enum security_types)lp_security(); + + is_our_domain = strequal(domain->name, lp_workgroup()); + + /* If not our domain, get the preferred DC, by asking our primary DC */ + if ( !is_our_domain + && get_dc_name_via_netlogon(domain, dcname, &ss, request_flags) + && add_one_dc_unique(mem_ctx, domain->name, dcname, &ss, dcs, + num_dcs) ) + { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), &ss); + DEBUG(10, ("Retrieved DC %s at %s via netlogon\n", + dcname, addr)); + return True; + } + + if ((sec == SEC_ADS) && (domain->alt_name != NULL)) { + char *sitename = NULL; + + /* We need to make sure we know the local site before + doing any DNS queries, as this will restrict the + get_sorted_dc_list() call below to only fetching + DNS records for the correct site. */ + + /* Find any DC to get the site record. + We deliberately don't care about the + return here. */ + + get_dc_name(domain->name, domain->alt_name, dcname, &ss); + + sitename = sitename_fetch(mem_ctx, domain->alt_name); + if (sitename) { + + /* Do the site-specific AD dns lookup first. */ + (void)get_sorted_dc_list(mem_ctx, + domain->alt_name, + sitename, + &sa_list, + &salist_size, + true); + + /* Add ips to the DC array. We don't look up the name + of the DC in this function, but we fill in the char* + of the ip now to make the failed connection cache + work */ + for ( i=0; i<salist_size; i++ ) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), + &sa_list[i].u.ss); + add_one_dc_unique(mem_ctx, + domain->name, + addr, + &sa_list[i].u.ss, + dcs, + num_dcs); + } + + TALLOC_FREE(sa_list); + TALLOC_FREE(sitename); + salist_size = 0; + } + + /* Now we add DCs from the main AD DNS lookup. */ + (void)get_sorted_dc_list(mem_ctx, + domain->alt_name, + NULL, + &sa_list, + &salist_size, + true); + + for ( i=0; i<salist_size; i++ ) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), + &sa_list[i].u.ss); + add_one_dc_unique(mem_ctx, + domain->name, + addr, + &sa_list[i].u.ss, + dcs, + num_dcs); + } + + TALLOC_FREE(sa_list); + salist_size = 0; + } + + /* Try standard netbios queries if no ADS and fall back to DNS queries + * if alt_name is available */ + if (*num_dcs == 0) { + (void)get_sorted_dc_list(mem_ctx, + domain->name, + NULL, + &sa_list, + &salist_size, + false); + if (salist_size == 0) { + if (domain->alt_name != NULL) { + (void)get_sorted_dc_list(mem_ctx, + domain->alt_name, + NULL, + &sa_list, + &salist_size, + true); + } + } + + for ( i=0; i<salist_size; i++ ) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), + &sa_list[i].u.ss); + add_one_dc_unique(mem_ctx, + domain->name, + addr, + &sa_list[i].u.ss, + dcs, + num_dcs); + } + + TALLOC_FREE(sa_list); + salist_size = 0; + } + + return True; +} + +static bool connect_preferred_dc(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + uint32_t request_flags, + int *fd) +{ + char *saf_servername = NULL; + NTSTATUS status; + bool ok; + + /* + * We have to check the server affinity cache here since later we select + * a DC based on response time and not preference. + */ + if (domain->force_dc) { + saf_servername = domain->dcname; + } else { + saf_servername = saf_fetch(mem_ctx, domain->name); + } + + /* + * Check the negative connection cache before talking to it. It going + * down may have triggered the reconnection. + */ + if (saf_servername != NULL) { + status = check_negative_conn_cache(domain->name, + saf_servername); + if (!NT_STATUS_IS_OK(status)) { + saf_servername = NULL; + } + } + + if (saf_servername != NULL) { + DBG_DEBUG("saf_servername is '%s' for domain %s\n", + saf_servername, domain->name); + + /* convert an ip address to a name */ + if (is_ipaddress(saf_servername)) { + ok = interpret_string_addr(&domain->dcaddr, + saf_servername, + AI_NUMERICHOST); + if (!ok) { + return false; + } + } else { + ok = resolve_name(saf_servername, + &domain->dcaddr, + 0x20, + true); + if (!ok) { + goto fail; + } + } + + TALLOC_FREE(domain->dcname); + ok = dcip_check_name(domain, + domain, + &domain->dcaddr, + &domain->dcname, + request_flags); + if (!ok) { + goto fail; + } + } + + if (domain->dcname == NULL) { + return false; + } + + status = check_negative_conn_cache(domain->name, domain->dcname); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + + status = smbsock_connect(&domain->dcaddr, 0, + NULL, -1, NULL, -1, + fd, NULL, 10); + if (!NT_STATUS_IS_OK(status)) { + winbind_add_failed_connection_entry(domain, + domain->dcname, + NT_STATUS_UNSUCCESSFUL); + return false; + } + return true; + +fail: + winbind_add_failed_connection_entry(domain, + saf_servername, + NT_STATUS_UNSUCCESSFUL); + return false; + +} + +/******************************************************************* + Find and make a connection to a DC in the given domain. + + @param[in] mem_ctx talloc memory context to allocate from + @param[in] domain domain to find a dc in + @param[out] fd fd of the open socket connected to the newly found dc + @return true when a DC connection is made, false otherwise +*******************************************************************/ + +static bool find_dc(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + uint32_t request_flags, + int *fd) +{ + struct dc_name_ip *dcs = NULL; + int num_dcs = 0; + + const char **dcnames = NULL; + size_t num_dcnames = 0; + + struct sockaddr_storage *addrs = NULL; + int num_addrs = 0; + + int i; + size_t fd_index; + + NTSTATUS status; + bool ok; + + *fd = -1; + + D_NOTICE("First try to connect to the closest DC (using server " + "affinity cache). If this fails, try to lookup the DC using " + "DNS afterwards.\n"); + ok = connect_preferred_dc(mem_ctx, domain, request_flags, fd); + if (ok) { + return true; + } + + if (domain->force_dc) { + return false; + } + + again: + D_DEBUG("Retrieving a list of IP addresses for DCs.\n"); + if (!get_dcs(mem_ctx, domain, &dcs, &num_dcs, request_flags) || (num_dcs == 0)) + return False; + + D_DEBUG("Retrieved IP addresses for %d DCs.\n", num_dcs); + for (i=0; i<num_dcs; i++) { + + if (!add_string_to_array(mem_ctx, dcs[i].name, + &dcnames, &num_dcnames)) { + return False; + } + if (!add_sockaddr_to_array(mem_ctx, &dcs[i].ss, TCP_SMB_PORT, + &addrs, &num_addrs)) { + return False; + } + } + + if ((num_dcnames == 0) || (num_dcnames != num_addrs)) + return False; + + if ((addrs == NULL) || (dcnames == NULL)) + return False; + + D_DEBUG("Trying to establish a connection to one of the %d DCs " + "(timeout of 10 sec for each DC).\n", + num_dcs); + status = smbsock_any_connect(addrs, dcnames, NULL, NULL, NULL, + num_addrs, 0, 10, fd, &fd_index, NULL); + if (!NT_STATUS_IS_OK(status)) { + for (i=0; i<num_dcs; i++) { + char ab[INET6_ADDRSTRLEN]; + print_sockaddr(ab, sizeof(ab), &dcs[i].ss); + DBG_DEBUG("smbsock_any_connect failed for " + "domain %s address %s. Error was %s\n", + domain->name, ab, nt_errstr(status)); + winbind_add_failed_connection_entry(domain, + dcs[i].name, NT_STATUS_UNSUCCESSFUL); + } + return False; + } + D_NOTICE("Successfully connected to DC '%s'.\n", dcs[fd_index].name); + + domain->dcaddr = addrs[fd_index]; + + if (*dcnames[fd_index] != '\0' && !is_ipaddress(dcnames[fd_index])) { + /* Ok, we've got a name for the DC */ + TALLOC_FREE(domain->dcname); + domain->dcname = talloc_strdup(domain, dcnames[fd_index]); + if (domain->dcname == NULL) { + return false; + } + return true; + } + + /* Try to figure out the name */ + TALLOC_FREE(domain->dcname); + ok = dcip_check_name(domain, + domain, + &domain->dcaddr, + &domain->dcname, + request_flags); + if (ok) { + return true; + } + + /* We can not continue without the DC's name */ + winbind_add_failed_connection_entry(domain, dcs[fd_index].name, + NT_STATUS_UNSUCCESSFUL); + + /* Throw away all arrays as we're doing this again. */ + TALLOC_FREE(dcs); + num_dcs = 0; + + TALLOC_FREE(dcnames); + num_dcnames = 0; + + TALLOC_FREE(addrs); + num_addrs = 0; + + if (*fd != -1) { + close(*fd); + *fd = -1; + } + + /* + * This should not be an infinite loop, since get_dcs() will not return + * the DC added to the negative connection cache in the above + * winbind_add_failed_connection_entry() call. + */ + goto again; +} + +static char *current_dc_key(TALLOC_CTX *mem_ctx, const char *domain_name) +{ + return talloc_asprintf_strupper_m(mem_ctx, "CURRENT_DCNAME/%s", + domain_name); +} + +static void store_current_dc_in_gencache(const char *domain_name, + const char *dc_name, + struct cli_state *cli) +{ + char addr[INET6_ADDRSTRLEN]; + char *key = NULL; + char *value = NULL; + + if (!cli_state_is_connected(cli)) { + return; + } + + print_sockaddr(addr, sizeof(addr), + smbXcli_conn_remote_sockaddr(cli->conn)); + + key = current_dc_key(talloc_tos(), domain_name); + if (key == NULL) { + goto done; + } + + value = talloc_asprintf(talloc_tos(), "%s %s", addr, dc_name); + if (value == NULL) { + goto done; + } + + gencache_set(key, value, 0x7fffffff); +done: + TALLOC_FREE(value); + TALLOC_FREE(key); +} + +bool fetch_current_dc_from_gencache(TALLOC_CTX *mem_ctx, + const char *domain_name, + char **p_dc_name, char **p_dc_ip) +{ + char *key, *p; + char *value = NULL; + bool ret = false; + char *dc_name = NULL; + char *dc_ip = NULL; + + key = current_dc_key(talloc_tos(), domain_name); + if (key == NULL) { + goto done; + } + if (!gencache_get(key, mem_ctx, &value, NULL)) { + goto done; + } + p = strchr(value, ' '); + if (p == NULL) { + goto done; + } + dc_ip = talloc_strndup(mem_ctx, value, p - value); + if (dc_ip == NULL) { + goto done; + } + dc_name = talloc_strdup(mem_ctx, p+1); + if (dc_name == NULL) { + goto done; + } + + if (p_dc_ip != NULL) { + *p_dc_ip = dc_ip; + dc_ip = NULL; + } + if (p_dc_name != NULL) { + *p_dc_name = dc_name; + dc_name = NULL; + } + ret = true; +done: + TALLOC_FREE(dc_name); + TALLOC_FREE(dc_ip); + TALLOC_FREE(key); + TALLOC_FREE(value); + return ret; +} + +NTSTATUS wb_open_internal_pipe(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *table, + struct rpc_pipe_client **ret_pipe) +{ + struct rpc_pipe_client *cli = NULL; + const struct auth_session_info *session_info = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + + session_info = get_session_info_system(); + SMB_ASSERT(session_info != NULL); + + status = rpc_pipe_open_local_np( + mem_ctx, table, NULL, NULL, NULL, NULL, session_info, &cli); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (ret_pipe) { + *ret_pipe = cli; + } + + return NT_STATUS_OK; +} + +static NTSTATUS cm_open_connection(struct winbindd_domain *domain, + struct winbindd_cm_conn *new_conn, + bool need_rw_dc) +{ + TALLOC_CTX *mem_ctx; + NTSTATUS result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + int retries; + uint32_t request_flags = need_rw_dc ? DS_WRITABLE_REQUIRED : 0; + int fd = -1; + bool retry = false; + bool seal_pipes = true; + + if ((mem_ctx = talloc_init("cm_open_connection")) == NULL) { + set_domain_offline(domain); + return NT_STATUS_NO_MEMORY; + } + + D_NOTICE("Creating connection to domain controller. This is a start of " + "a new connection or a DC failover. The failover only happens " + "if the domain has more than one DC. We will try to connect 3 " + "times at most.\n"); + for (retries = 0; retries < 3; retries++) { + bool found_dc; + + D_DEBUG("Attempt %d/3: DC '%s' of domain '%s'.\n", + retries, + domain->dcname ? domain->dcname : "", + domain->name); + + found_dc = find_dc(mem_ctx, domain, request_flags, &fd); + if (!found_dc) { + /* This is the one place where we will + set the global winbindd offline state + to true, if a "WINBINDD_OFFLINE" entry + is found in the winbindd cache. */ + set_global_winbindd_state_offline(); + result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + break; + } + + new_conn->cli = NULL; + + result = cm_prepare_connection(domain, fd, domain->dcname, + &new_conn->cli, &retry); + if (NT_STATUS_IS_OK(result)) { + break; + } + if (!retry) { + break; + } + } + + if (!NT_STATUS_IS_OK(result)) { + /* Ensure we setup the retry handler. */ + set_domain_offline(domain); + goto out; + } + + winbindd_set_locator_kdc_envs(domain); + + if (domain->online == False) { + /* We're changing state from offline to online. */ + set_global_winbindd_state_online(); + } + set_domain_online(domain); + + /* + * Much as I hate global state, this seems to be the point + * where we can be certain that we have a proper connection to + * a DC. wbinfo --dc-info needs that information, store it in + * gencache with a looong timeout. This will need revisiting + * once we start to connect to multiple DCs, wbcDcInfo is + * already prepared for that. + */ + store_current_dc_in_gencache(domain->name, domain->dcname, + new_conn->cli); + + seal_pipes = lp_winbind_sealed_pipes(); + seal_pipes = lp_parm_bool(-1, "winbind sealed pipes", + domain->name, + seal_pipes); + + if (seal_pipes) { + new_conn->auth_level = DCERPC_AUTH_LEVEL_PRIVACY; + } else { + new_conn->auth_level = DCERPC_AUTH_LEVEL_INTEGRITY; + } + +out: + talloc_destroy(mem_ctx); + return result; +} + +/* Close down all open pipes on a connection. */ + +void invalidate_cm_connection(struct winbindd_domain *domain) +{ + NTSTATUS result; + struct winbindd_cm_conn *conn = &domain->conn; + + domain->sequence_number = DOM_SEQUENCE_NONE; + domain->last_seq_check = 0; + domain->last_status = NT_STATUS_SERVER_DISABLED; + + /* We're closing down a possibly dead + connection. Don't have impossibly long (10s) timeouts. */ + + if (conn->cli) { + cli_set_timeout(conn->cli, 1000); /* 1 second. */ + } + + if (conn->samr_pipe != NULL) { + if (is_valid_policy_hnd(&conn->sam_connect_handle)) { + dcerpc_samr_Close(conn->samr_pipe->binding_handle, + talloc_tos(), + &conn->sam_connect_handle, + &result); + } + TALLOC_FREE(conn->samr_pipe); + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + + if (conn->lsa_pipe != NULL) { + if (is_valid_policy_hnd(&conn->lsa_policy)) { + dcerpc_lsa_Close(conn->lsa_pipe->binding_handle, + talloc_tos(), + &conn->lsa_policy, + &result); + } + TALLOC_FREE(conn->lsa_pipe); + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + + if (conn->lsa_pipe_tcp != NULL) { + if (is_valid_policy_hnd(&conn->lsa_policy)) { + dcerpc_lsa_Close(conn->lsa_pipe_tcp->binding_handle, + talloc_tos(), + &conn->lsa_policy, + &result); + } + TALLOC_FREE(conn->lsa_pipe_tcp); + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + + if (conn->netlogon_pipe != NULL) { + TALLOC_FREE(conn->netlogon_pipe); + /* Ok, it must be dead. Drop timeout to 0.5 sec. */ + if (conn->cli) { + cli_set_timeout(conn->cli, 500); + } + } + + conn->auth_level = DCERPC_AUTH_LEVEL_PRIVACY; + TALLOC_FREE(conn->netlogon_creds_ctx); + + if (conn->cli) { + cli_shutdown(conn->cli); + } + + conn->cli = NULL; +} + +void close_conns_after_fork(void) +{ + struct winbindd_domain *domain; + struct winbindd_cli_state *cli_state; + + for (domain = domain_list(); domain; domain = domain->next) { + /* + * first close the low level SMB TCP connection + * so that we don't generate any SMBclose + * requests in invalidate_cm_connection() + */ + if (cli_state_is_connected(domain->conn.cli)) { + smbXcli_conn_disconnect(domain->conn.cli->conn, NT_STATUS_OK); + } + + invalidate_cm_connection(domain); + } + + for (cli_state = winbindd_client_list(); + cli_state != NULL; + cli_state = cli_state->next) { + if (cli_state->sock >= 0) { + close(cli_state->sock); + cli_state->sock = -1; + } + } +} + +static bool connection_ok(struct winbindd_domain *domain) +{ + bool ok; + + ok = cli_state_is_connected(domain->conn.cli); + if (!ok) { + DEBUG(3, ("connection_ok: Connection to %s for domain %s is not connected\n", + domain->dcname, domain->name)); + return False; + } + + if (!domain->online) { + DEBUG(3, ("connection_ok: Domain %s is offline\n", domain->name)); + return False; + } + + return True; +} + +/* Initialize a new connection up to the RPC BIND. + Bypass online status check so always does network calls. */ + +static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain, bool need_rw_dc) +{ + NTSTATUS result; + bool skip_connection = domain->internal; + if (need_rw_dc && domain->rodc) { + skip_connection = false; + } + + /* Internal connections never use the network. */ + if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + /* Still ask the internal LSA and SAMR server about the local domain */ + if (skip_connection || connection_ok(domain)) { + if (!domain->initialized) { + set_dc_type_and_flags(domain); + } + return NT_STATUS_OK; + } + + invalidate_cm_connection(domain); + + if (!domain->primary && !domain->initialized) { + /* + * Before we connect to a trust, work out if it is an + * AD domain by asking our own domain. + */ + set_dc_type_and_flags_trustinfo(domain); + } + + result = cm_open_connection(domain, &domain->conn, need_rw_dc); + + if (NT_STATUS_IS_OK(result) && !domain->initialized) { + set_dc_type_and_flags(domain); + } + + return result; +} + +NTSTATUS init_dc_connection(struct winbindd_domain *domain, bool need_rw_dc) +{ + if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + SMB_ASSERT(wb_child_domain() || idmap_child()); + + return init_dc_connection_network(domain, need_rw_dc); +} + +static NTSTATUS init_dc_connection_rpc(struct winbindd_domain *domain, bool need_rw_dc) +{ + NTSTATUS status; + + status = init_dc_connection(domain, need_rw_dc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!domain->internal && domain->conn.cli == NULL) { + /* happens for trusted domains without inbound trust */ + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + + return NT_STATUS_OK; +} + +/****************************************************************************** + Set the trust flags (direction and forest location) for a domain +******************************************************************************/ + +static bool set_dc_type_and_flags_trustinfo( struct winbindd_domain *domain ) +{ + struct winbindd_domain *our_domain; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + WERROR werr; + struct netr_DomainTrustList trusts; + int i; + uint32_t flags = (NETR_TRUST_FLAG_IN_FOREST | + NETR_TRUST_FLAG_OUTBOUND | + NETR_TRUST_FLAG_INBOUND); + struct rpc_pipe_client *cli; + TALLOC_CTX *mem_ctx = NULL; + struct dcerpc_binding_handle *b; + + if (IS_DC) { + /* + * On a DC we loaded all trusts + * from configuration and never learn + * new domains. + */ + return true; + } + + DEBUG(5, ("set_dc_type_and_flags_trustinfo: domain %s\n", domain->name )); + + /* Our primary domain doesn't need to worry about trust flags. + Force it to go through the network setup */ + if ( domain->primary ) { + return False; + } + + mem_ctx = talloc_stackframe(); + our_domain = find_our_domain(); + if (our_domain->internal) { + result = init_dc_connection(our_domain, false); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(3,("set_dc_type_and_flags_trustinfo: " + "Not able to make a connection to our domain: %s\n", + nt_errstr(result))); + TALLOC_FREE(mem_ctx); + return false; + } + } + + /* This won't work unless our domain is AD */ + if ( !our_domain->active_directory ) { + TALLOC_FREE(mem_ctx); + return False; + } + + if (our_domain->internal) { + result = wb_open_internal_pipe(mem_ctx, &ndr_table_netlogon, &cli); + } else if (!connection_ok(our_domain)) { + DEBUG(3,("set_dc_type_and_flags_trustinfo: " + "No connection to our domain!\n")); + TALLOC_FREE(mem_ctx); + return False; + } else { + result = cm_connect_netlogon(our_domain, &cli); + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(5, ("set_dc_type_and_flags_trustinfo: Could not open " + "a connection to %s for PIPE_NETLOGON (%s)\n", + domain->name, nt_errstr(result))); + TALLOC_FREE(mem_ctx); + return False; + } + b = cli->binding_handle; + + /* Use DsEnumerateDomainTrusts to get us the trust direction and type. */ + result = dcerpc_netr_DsrEnumerateDomainTrusts(b, mem_ctx, + cli->desthost, + flags, + &trusts, + &werr); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("set_dc_type_and_flags_trustinfo: " + "failed to query trusted domain list: %s\n", + nt_errstr(result))); + TALLOC_FREE(mem_ctx); + return false; + } + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,("set_dc_type_and_flags_trustinfo: " + "failed to query trusted domain list: %s\n", + win_errstr(werr))); + TALLOC_FREE(mem_ctx); + return false; + } + + /* Now find the domain name and get the flags */ + + for ( i=0; i<trusts.count; i++ ) { + if ( strequal( domain->name, trusts.array[i].netbios_name) ) { + domain->domain_flags = trusts.array[i].trust_flags; + domain->domain_type = trusts.array[i].trust_type; + domain->domain_trust_attribs = trusts.array[i].trust_attributes; + + if ( domain->domain_type == LSA_TRUST_TYPE_UPLEVEL ) + domain->active_directory = True; + + /* This flag is only set if the domain is *our* + primary domain and the primary domain is in + native mode */ + + domain->native_mode = (domain->domain_flags & NETR_TRUST_FLAG_NATIVE); + + DEBUG(5, ("set_dc_type_and_flags_trustinfo: domain %s is %sin " + "native mode.\n", domain->name, + domain->native_mode ? "" : "NOT ")); + + DEBUG(5,("set_dc_type_and_flags_trustinfo: domain %s is %s" + "running active directory.\n", domain->name, + domain->active_directory ? "" : "NOT ")); + + domain->can_do_ncacn_ip_tcp = domain->active_directory; + + domain->initialized = True; + + break; + } + } + + TALLOC_FREE(mem_ctx); + + return domain->initialized; +} + +/****************************************************************************** + We can 'sense' certain things about the DC by it's replies to certain + questions. + + This tells us if this particular remote server is Active Directory, and if it + is native mode. +******************************************************************************/ + +static void set_dc_type_and_flags_connect( struct winbindd_domain *domain ) +{ + NTSTATUS status, result; + NTSTATUS close_status = NT_STATUS_UNSUCCESSFUL; + WERROR werr; + TALLOC_CTX *mem_ctx = NULL; + struct rpc_pipe_client *cli = NULL; + struct policy_handle pol = { .handle_type = 0 }; + union dssetup_DsRoleInfo info; + union lsa_PolicyInformation *lsa_info = NULL; + union lsa_revision_info out_revision_info = { + .info1 = { + .revision = 0, + }, + }; + uint32_t out_version = 0; + + if (!domain->internal && !connection_ok(domain)) { + return; + } + + mem_ctx = talloc_init("set_dc_type_and_flags on domain %s\n", + domain->name); + if (!mem_ctx) { + DEBUG(1, ("set_dc_type_and_flags_connect: talloc_init() failed\n")); + return; + } + + DEBUG(5, ("set_dc_type_and_flags_connect: domain %s\n", domain->name )); + + if (domain->internal) { + status = wb_open_internal_pipe(mem_ctx, + &ndr_table_dssetup, + &cli); + } else { + status = cli_rpc_pipe_open_noauth(domain->conn.cli, + &ndr_table_dssetup, + &cli); + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to " + "PI_DSSETUP on domain %s: (%s)\n", + domain->name, nt_errstr(status))); + + /* if this is just a non-AD domain we need to continue + * identifying so that we can in the end return with + * domain->initialized = True - gd */ + + goto no_dssetup; + } + + status = dcerpc_dssetup_DsRoleGetPrimaryDomainInformation(cli->binding_handle, mem_ctx, + DS_ROLE_BASIC_INFORMATION, + &info, + &werr); + TALLOC_FREE(cli); + + if (NT_STATUS_IS_OK(status)) { + result = werror_to_ntstatus(werr); + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("set_dc_type_and_flags_connect: rpccli_ds_getprimarydominfo " + "on domain %s failed: (%s)\n", + domain->name, nt_errstr(status))); + + /* older samba3 DCs will return DCERPC_FAULT_OP_RNG_ERROR for + * every opcode on the DSSETUP pipe, continue with + * no_dssetup mode here as well to get domain->initialized + * set - gd */ + + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + goto no_dssetup; + } + + TALLOC_FREE(mem_ctx); + return; + } + + if ((info.basic.flags & DS_ROLE_PRIMARY_DS_RUNNING) && + !(info.basic.flags & DS_ROLE_PRIMARY_DS_MIXED_MODE)) { + domain->native_mode = True; + } else { + domain->native_mode = False; + } + +no_dssetup: + if (domain->internal) { + status = wb_open_internal_pipe(mem_ctx, + &ndr_table_lsarpc, + &cli); + } else { + status = cli_rpc_pipe_open_noauth(domain->conn.cli, + &ndr_table_lsarpc, &cli); + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to " + "PI_LSARPC on domain %s: (%s)\n", + domain->name, nt_errstr(status))); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + + status = dcerpc_lsa_open_policy_fallback(cli->binding_handle, + mem_ctx, + cli->srv_name_slash, + true, + SEC_FLAG_MAXIMUM_ALLOWED, + &out_version, + &out_revision_info, + &pol, + &result); + + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + /* This particular query is exactly what Win2k clients use + to determine that the DC is active directory */ + status = dcerpc_lsa_QueryInfoPolicy2(cli->binding_handle, mem_ctx, + &pol, + LSA_POLICY_INFO_DNS, + &lsa_info, + &result); + } + + /* + * If the status and result will not be OK we will fallback to + * OpenPolicy. + */ + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + domain->active_directory = True; + + if (lsa_info->dns.name.string) { + if (!strequal(domain->name, lsa_info->dns.name.string)) + { + DEBUG(1, ("set_dc_type_and_flags_connect: DC " + "for domain %s claimed it was a DC " + "for domain %s, refusing to " + "initialize\n", + domain->name, + lsa_info->dns.name.string)); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + talloc_free(domain->name); + domain->name = talloc_strdup(domain, + lsa_info->dns.name.string); + if (domain->name == NULL) { + goto done; + } + } + + if (lsa_info->dns.dns_domain.string) { + if (domain->alt_name != NULL && + !strequal(domain->alt_name, + lsa_info->dns.dns_domain.string)) + { + DEBUG(1, ("set_dc_type_and_flags_connect: DC " + "for domain %s (%s) claimed it was " + "a DC for domain %s, refusing to " + "initialize\n", + domain->alt_name, domain->name, + lsa_info->dns.dns_domain.string)); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + talloc_free(domain->alt_name); + domain->alt_name = + talloc_strdup(domain, + lsa_info->dns.dns_domain.string); + if (domain->alt_name == NULL) { + goto done; + } + } + + /* See if we can set some domain trust flags about + ourself */ + + if (lsa_info->dns.dns_forest.string) { + talloc_free(domain->forest_name); + domain->forest_name = + talloc_strdup(domain, + lsa_info->dns.dns_forest.string); + if (domain->forest_name == NULL) { + goto done; + } + + if (strequal(domain->forest_name, domain->alt_name)) { + domain->domain_flags |= NETR_TRUST_FLAG_TREEROOT; + } + } + + if (lsa_info->dns.sid) { + if (!is_null_sid(&domain->sid) && + !dom_sid_equal(&domain->sid, + lsa_info->dns.sid)) + { + struct dom_sid_buf buf1, buf2; + DEBUG(1, ("set_dc_type_and_flags_connect: DC " + "for domain %s (%s) claimed it was " + "a DC for domain %s, refusing to " + "initialize\n", + dom_sid_str_buf(&domain->sid, &buf1), + domain->name, + dom_sid_str_buf(lsa_info->dns.sid, + &buf2))); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + sid_copy(&domain->sid, lsa_info->dns.sid); + } + } else { + domain->active_directory = False; + + status = rpccli_lsa_open_policy(cli, mem_ctx, True, + SEC_FLAG_MAXIMUM_ALLOWED, + &pol); + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dcerpc_lsa_QueryInfoPolicy(cli->binding_handle, mem_ctx, + &pol, + LSA_POLICY_INFO_ACCOUNT_DOMAIN, + &lsa_info, + &result); + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + + if (lsa_info->account_domain.name.string) { + if (!strequal(domain->name, + lsa_info->account_domain.name.string)) + { + DEBUG(1, + ("set_dc_type_and_flags_connect: " + "DC for domain %s claimed it was" + " a DC for domain %s, refusing " + "to initialize\n", domain->name, + lsa_info-> + account_domain.name.string)); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + talloc_free(domain->name); + domain->name = + talloc_strdup(domain, + lsa_info->account_domain.name.string); + } + + if (lsa_info->account_domain.sid) { + if (!is_null_sid(&domain->sid) && + !dom_sid_equal(&domain->sid, + lsa_info->account_domain.sid)) + { + struct dom_sid_buf buf1, buf2; + DEBUG(1, + ("set_dc_type_and_flags_connect: " + "DC for domain %s (%s) claimed " + "it was a DC for domain %s, " + "refusing to initialize\n", + dom_sid_str_buf( + &domain->sid, &buf1), + domain->name, + dom_sid_str_buf( + lsa_info->account_domain.sid, + &buf2))); + TALLOC_FREE(cli); + TALLOC_FREE(mem_ctx); + return; + } + sid_copy(&domain->sid, lsa_info->account_domain.sid); + } + } + } +done: + if (is_valid_policy_hnd(&pol)) { + dcerpc_lsa_Close(cli->binding_handle, + mem_ctx, + &pol, + &close_status); + } + + DEBUG(5, ("set_dc_type_and_flags_connect: domain %s is %sin native mode.\n", + domain->name, domain->native_mode ? "" : "NOT ")); + + DEBUG(5,("set_dc_type_and_flags_connect: domain %s is %srunning active directory.\n", + domain->name, domain->active_directory ? "" : "NOT ")); + + domain->can_do_ncacn_ip_tcp = domain->active_directory; + + TALLOC_FREE(cli); + + TALLOC_FREE(mem_ctx); + + domain->initialized = True; +} + +/********************************************************************** + Set the domain_flags (trust attributes, domain operating modes, etc... +***********************************************************************/ + +static void set_dc_type_and_flags( struct winbindd_domain *domain ) +{ + if (IS_DC) { + /* + * On a DC we loaded all trusts + * from configuration and never learn + * new domains. + */ + return; + } + + /* we always have to contact our primary domain */ + + if ( domain->primary || domain->internal) { + DEBUG(10,("set_dc_type_and_flags: setting up flags for " + "primary or internal domain\n")); + set_dc_type_and_flags_connect( domain ); + return; + } + + /* Use our DC to get the information if possible */ + + if ( !set_dc_type_and_flags_trustinfo( domain ) ) { + /* Otherwise, fallback to contacting the + domain directly */ + set_dc_type_and_flags_connect( domain ); + } + + return; +} + + + +/********************************************************************** +***********************************************************************/ + +static NTSTATUS cm_get_schannel_creds(struct winbindd_domain *domain, + struct netlogon_creds_cli_context **ppdc) +{ + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + struct rpc_pipe_client *netlogon_pipe; + + *ppdc = NULL; + + if ((!IS_DC) && (!domain->primary)) { + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + + if (domain->conn.netlogon_creds_ctx != NULL) { + *ppdc = domain->conn.netlogon_creds_ctx; + return NT_STATUS_OK; + } + + result = cm_connect_netlogon_secure(domain, &netlogon_pipe, ppdc); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + return NT_STATUS_OK; +} + +NTSTATUS cm_connect_sam(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, + bool need_rw_dc, + struct rpc_pipe_client **cli, struct policy_handle *sam_handle) +{ + struct winbindd_cm_conn *conn; + NTSTATUS status, result; + struct netlogon_creds_cli_context *p_creds; + struct cli_credentials *creds = NULL; + bool retry = false; /* allow one retry attempt for expired session */ + const char *remote_name = NULL; + const struct sockaddr_storage *remote_sockaddr = NULL; + bool sealed_pipes = true; + bool strong_key = true; + + if (sid_check_is_our_sam(&domain->sid)) { + if (domain->rodc == false || need_rw_dc == false) { + return open_internal_samr_conn(mem_ctx, domain, cli, sam_handle); + } + } + + if (IS_AD_DC) { + /* + * In theory we should not use SAMR within + * winbindd at all, but that's a larger task to + * remove this and avoid breaking existing + * setups. + * + * At least as AD DC we have the restriction + * to avoid SAMR against trusted domains, + * as there're no existing setups. + */ + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + +retry: + status = init_dc_connection_rpc(domain, need_rw_dc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + conn = &domain->conn; + + if (rpccli_is_connected(conn->samr_pipe)) { + goto done; + } + + TALLOC_FREE(conn->samr_pipe); + + /* + * No SAMR pipe yet. Attempt to get an NTLMSSP SPNEGO authenticated + * sign and sealed pipe using the machine account password by + * preference. If we can't - try schannel, if that fails, try + * anonymous. + */ + + result = get_trust_credentials(domain, talloc_tos(), false, &creds); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10, ("cm_connect_sam: No user available for " + "domain %s, trying schannel\n", domain->name)); + goto schannel; + } + + if (cli_credentials_is_anonymous(creds)) { + goto anonymous; + } + + remote_name = smbXcli_conn_remote_name(conn->cli->conn); + remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn); + + /* + * We have an authenticated connection. Use a SPNEGO + * authenticated SAMR pipe with sign & seal. + */ + status = cli_rpc_pipe_open_with_creds(conn->cli, + &ndr_table_samr, + NCACN_NP, + DCERPC_AUTH_TYPE_SPNEGO, + conn->auth_level, + remote_name, + remote_sockaddr, + creds, + &conn->samr_pipe); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("cm_connect_sam: failed to connect to SAMR " + "pipe for domain %s using NTLMSSP " + "authenticated pipe: user %s. Error was " + "%s\n", domain->name, + cli_credentials_get_unparsed_name(creds, talloc_tos()), + nt_errstr(status))); + goto schannel; + } + + DEBUG(10,("cm_connect_sam: connected to SAMR pipe for " + "domain %s using NTLMSSP authenticated " + "pipe: user %s\n", domain->name, + cli_credentials_get_unparsed_name(creds, talloc_tos()))); + + status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx, + conn->samr_pipe->desthost, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->sam_connect_handle, + &result); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->samr_pipe); + retry = true; + goto retry; + } + + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + goto open_domain; + } + if (NT_STATUS_IS_OK(status)) { + status = result; + } + + DEBUG(10,("cm_connect_sam: ntlmssp-sealed dcerpc_samr_Connect2 " + "failed for domain %s, error was %s. Trying schannel\n", + domain->name, nt_errstr(status) )); + TALLOC_FREE(conn->samr_pipe); + + schannel: + + /* Fall back to schannel if it's a W2K pre-SP1 box. */ + + status = cm_get_schannel_creds(domain, &p_creds); + if (!NT_STATUS_IS_OK(status)) { + /* If this call fails - conn->cli can now be NULL ! */ + DEBUG(10, ("cm_connect_sam: Could not get schannel auth info " + "for domain %s (error %s), trying anon\n", + domain->name, + nt_errstr(status) )); + goto anonymous; + } + TALLOC_FREE(creds); + status = cli_rpc_pipe_open_schannel_with_creds( + conn->cli, &ndr_table_samr, NCACN_NP, p_creds, + remote_name, + remote_sockaddr, + &conn->samr_pipe); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("cm_connect_sam: failed to connect to SAMR pipe for " + "domain %s using schannel. Error was %s\n", + domain->name, nt_errstr(status) )); + goto anonymous; + } + DEBUG(10,("cm_connect_sam: connected to SAMR pipe for domain %s using " + "schannel.\n", domain->name )); + + status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx, + conn->samr_pipe->desthost, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->sam_connect_handle, + &result); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->samr_pipe); + retry = true; + goto retry; + } + + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + goto open_domain; + } + if (NT_STATUS_IS_OK(status)) { + status = result; + } + DEBUG(10,("cm_connect_sam: schannel-sealed dcerpc_samr_Connect2 failed " + "for domain %s, error was %s. Trying anonymous\n", + domain->name, nt_errstr(status) )); + TALLOC_FREE(conn->samr_pipe); + + anonymous: + + sealed_pipes = lp_winbind_sealed_pipes(); + sealed_pipes = lp_parm_bool(-1, "winbind sealed pipes", + domain->name, + sealed_pipes); + strong_key = lp_require_strong_key(); + strong_key = lp_parm_bool(-1, "require strong key", + domain->name, + strong_key); + + /* Finally fall back to anonymous. */ + if (sealed_pipes || strong_key) { + status = NT_STATUS_DOWNGRADE_DETECTED; + DEBUG(1, ("Unwilling to make SAMR connection to domain %s " + "without connection level security, " + "must set 'winbind sealed pipes:%s = false' and " + "'require strong key:%s = false' to proceed: %s\n", + domain->name, domain->name, domain->name, + nt_errstr(status))); + goto done; + } + status = cli_rpc_pipe_open_noauth(conn->cli, &ndr_table_samr, + &conn->samr_pipe); + + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx, + conn->samr_pipe->desthost, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->sam_connect_handle, + &result); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->samr_pipe); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("cm_connect_sam: rpccli_samr_Connect2 failed " + "for domain %s Error was %s\n", + domain->name, nt_errstr(status) )); + goto done; + } + if (!NT_STATUS_IS_OK(result)) { + status = result; + DEBUG(10,("cm_connect_sam: dcerpc_samr_Connect2 failed " + "for domain %s Error was %s\n", + domain->name, nt_errstr(result))); + goto done; + } + + open_domain: + status = dcerpc_samr_OpenDomain(conn->samr_pipe->binding_handle, + mem_ctx, + &conn->sam_connect_handle, + SEC_FLAG_MAXIMUM_ALLOWED, + &domain->sid, + &conn->sam_domain_handle, + &result); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = result; + done: + + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + /* + * if we got access denied, we might just have no access rights + * to talk to the remote samr server server (e.g. when we are a + * PDC and we are connecting a w2k8 pdc via an interdomain + * trust). In that case do not invalidate the whole connection + * stack + */ + TALLOC_FREE(conn->samr_pipe); + ZERO_STRUCT(conn->sam_domain_handle); + return status; + } else if (!NT_STATUS_IS_OK(status)) { + invalidate_cm_connection(domain); + return status; + } + + *cli = conn->samr_pipe; + *sam_handle = conn->sam_domain_handle; + return status; +} + +/********************************************************************** + open an schanneld ncacn_ip_tcp connection to LSA +***********************************************************************/ + +static NTSTATUS cm_connect_lsa_tcp(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli) +{ + struct winbindd_cm_conn *conn; + struct netlogon_creds_cli_context *p_creds = NULL; + NTSTATUS status; + const char *remote_name = NULL; + const struct sockaddr_storage *remote_sockaddr = NULL; + + DEBUG(10,("cm_connect_lsa_tcp\n")); + + status = init_dc_connection_rpc(domain, false); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + conn = &domain->conn; + + /* + * rpccli_is_connected handles more error cases + */ + if (rpccli_is_connected(conn->lsa_pipe_tcp) && + conn->lsa_pipe_tcp->transport->transport == NCACN_IP_TCP && + conn->lsa_pipe_tcp->auth->auth_level >= DCERPC_AUTH_LEVEL_INTEGRITY) { + goto done; + } + + TALLOC_FREE(conn->lsa_pipe_tcp); + + status = cm_get_schannel_creds(domain, &p_creds); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + remote_name = smbXcli_conn_remote_name(conn->cli->conn); + remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn); + + status = cli_rpc_pipe_open_schannel_with_creds( + conn->cli, + &ndr_table_lsarpc, + NCACN_IP_TCP, + p_creds, + remote_name, + remote_sockaddr, + &conn->lsa_pipe_tcp); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("cli_rpc_pipe_open_schannel_with_key failed: %s\n", + nt_errstr(status))); + goto done; + } + + done: + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(conn->lsa_pipe_tcp); + return status; + } + + *cli = conn->lsa_pipe_tcp; + + return status; +} + +NTSTATUS cm_connect_lsa(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli, struct policy_handle *lsa_policy) +{ + struct winbindd_cm_conn *conn; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + struct netlogon_creds_cli_context *p_creds; + struct cli_credentials *creds = NULL; + bool retry = false; /* allow one retry attempt for expired session */ + const char *remote_name = NULL; + const struct sockaddr_storage *remote_sockaddr = NULL; + bool sealed_pipes = true; + bool strong_key = true; + +retry: + result = init_dc_connection_rpc(domain, false); + if (!NT_STATUS_IS_OK(result)) + return result; + + conn = &domain->conn; + + if (rpccli_is_connected(conn->lsa_pipe)) { + goto done; + } + + TALLOC_FREE(conn->lsa_pipe); + + if (IS_DC) { + /* + * Make sure we only use schannel as AD DC. + */ + goto schannel; + } + + result = get_trust_credentials(domain, talloc_tos(), false, &creds); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10, ("cm_connect_lsa: No user available for " + "domain %s, trying schannel\n", domain->name)); + goto schannel; + } + + if (cli_credentials_is_anonymous(creds)) { + goto anonymous; + } + + remote_name = smbXcli_conn_remote_name(conn->cli->conn); + remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn); + + /* + * We have an authenticated connection. Use a SPNEGO + * authenticated LSA pipe with sign & seal. + */ + result = cli_rpc_pipe_open_with_creds + (conn->cli, &ndr_table_lsarpc, NCACN_NP, + DCERPC_AUTH_TYPE_SPNEGO, + conn->auth_level, + remote_name, + remote_sockaddr, + creds, + &conn->lsa_pipe); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for " + "domain %s using NTLMSSP authenticated pipe: user " + "%s. Error was %s. Trying schannel.\n", + domain->name, + cli_credentials_get_unparsed_name(creds, talloc_tos()), + nt_errstr(result))); + goto schannel; + } + + DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using " + "NTLMSSP authenticated pipe: user %s\n", + domain->name, cli_credentials_get_unparsed_name(creds, talloc_tos()))); + + result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->lsa_policy); + if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->lsa_pipe); + retry = true; + goto retry; + } + + if (NT_STATUS_IS_OK(result)) { + goto done; + } + + DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying " + "schannel\n")); + + TALLOC_FREE(conn->lsa_pipe); + + schannel: + + /* Fall back to schannel if it's a W2K pre-SP1 box. */ + + result = cm_get_schannel_creds(domain, &p_creds); + if (!NT_STATUS_IS_OK(result)) { + /* If this call fails - conn->cli can now be NULL ! */ + DEBUG(10, ("cm_connect_lsa: Could not get schannel auth info " + "for domain %s (error %s), trying anon\n", + domain->name, + nt_errstr(result) )); + goto anonymous; + } + + TALLOC_FREE(creds); + result = cli_rpc_pipe_open_schannel_with_creds( + conn->cli, &ndr_table_lsarpc, NCACN_NP, p_creds, + remote_name, + remote_sockaddr, + &conn->lsa_pipe); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for " + "domain %s using schannel. Error was %s\n", + domain->name, nt_errstr(result) )); + goto anonymous; + } + DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using " + "schannel.\n", domain->name )); + + result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->lsa_policy); + + if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->lsa_pipe); + retry = true; + goto retry; + } + + if (NT_STATUS_IS_OK(result)) { + goto done; + } + + if (IS_DC) { + /* + * Make sure we only use schannel as AD DC. + */ + goto done; + } + + DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying " + "anonymous\n")); + + TALLOC_FREE(conn->lsa_pipe); + + anonymous: + + if (IS_DC) { + /* + * Make sure we only use schannel as AD DC. + */ + goto done; + } + + sealed_pipes = lp_winbind_sealed_pipes(); + sealed_pipes = lp_parm_bool(-1, "winbind sealed pipes", + domain->name, + sealed_pipes); + strong_key = lp_require_strong_key(); + strong_key = lp_parm_bool(-1, "require strong key", + domain->name, + strong_key); + + /* Finally fall back to anonymous. */ + if (sealed_pipes || strong_key) { + result = NT_STATUS_DOWNGRADE_DETECTED; + DEBUG(1, ("Unwilling to make LSA connection to domain %s " + "without connection level security, " + "must set 'winbind sealed pipes:%s = false' and " + "'require strong key:%s = false' to proceed: %s\n", + domain->name, domain->name, domain->name, + nt_errstr(result))); + goto done; + } + + result = cli_rpc_pipe_open_noauth(conn->cli, + &ndr_table_lsarpc, + &conn->lsa_pipe); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED) + && !retry) { + invalidate_cm_connection(domain); + retry = true; + goto retry; + } + + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True, + SEC_FLAG_MAXIMUM_ALLOWED, + &conn->lsa_policy); + + if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) { + invalidate_cm_connection(domain); + TALLOC_FREE(conn->lsa_pipe); + retry = true; + goto retry; + } + + done: + if (!NT_STATUS_IS_OK(result)) { + invalidate_cm_connection(domain); + return result; + } + + *cli = conn->lsa_pipe; + *lsa_policy = conn->lsa_policy; + return result; +} + +/**************************************************************************** +Open a LSA connection to a DC, suitable for LSA lookup calls. +****************************************************************************/ + +NTSTATUS cm_connect_lsat(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli, + struct policy_handle *lsa_policy) +{ + NTSTATUS status; + + if (domain->can_do_ncacn_ip_tcp) { + status = cm_connect_lsa_tcp(domain, mem_ctx, cli); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR) || + NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) { + invalidate_cm_connection(domain); + status = cm_connect_lsa_tcp(domain, mem_ctx, cli); + } + if (NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * we tried twice to connect via ncan_ip_tcp and schannel and + * failed - maybe it is a trusted domain we can't connect to ? + * do not try tcp next time - gd + * + * This also prevents NETLOGON over TCP + */ + domain->can_do_ncacn_ip_tcp = false; + } + + status = cm_connect_lsa(domain, mem_ctx, cli, lsa_policy); + + return status; +} + +/**************************************************************************** + Open the netlogon pipe to this DC. +****************************************************************************/ + +static NTSTATUS cm_connect_netlogon_transport(struct winbindd_domain *domain, + enum dcerpc_transport_t transport, + struct rpc_pipe_client **cli) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + struct winbindd_cm_conn *conn; + NTSTATUS result; + enum netr_SchannelType sec_chan_type; + struct cli_credentials *creds = NULL; + + *cli = NULL; + + if (IS_DC) { + if (domain->secure_channel_type == SEC_CHAN_NULL) { + /* + * Make sure we don't even try to + * connect to a foreign domain + * without a direct outbound trust. + */ + return NT_STATUS_NO_TRUST_LSA_SECRET; + } + } + + result = init_dc_connection_rpc(domain, domain->rodc); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + conn = &domain->conn; + + if (rpccli_is_connected(conn->netlogon_pipe)) { + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; + } + + TALLOC_FREE(conn->netlogon_pipe); + TALLOC_FREE(conn->netlogon_creds_ctx); + + result = get_trust_credentials(domain, talloc_tos(), true, &creds); + if (!NT_STATUS_IS_OK(result)) { + DBG_DEBUG("No user available for domain %s when trying " + "schannel\n", domain->name); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + if (cli_credentials_is_anonymous(creds)) { + DBG_WARNING("get_trust_credential only gave anonymous for %s, " + "unable to make get NETLOGON credentials\n", + domain->name); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + sec_chan_type = cli_credentials_get_secure_channel_type(creds); + if (sec_chan_type == SEC_CHAN_NULL) { + const char *remote_name = + smbXcli_conn_remote_name(conn->cli->conn); + const struct sockaddr_storage *remote_sockaddr = + smbXcli_conn_remote_sockaddr(conn->cli->conn); + + if (transport == NCACN_IP_TCP) { + DBG_NOTICE("get_secure_channel_type gave SEC_CHAN_NULL " + "for %s, deny NCACN_IP_TCP and let the " + "caller fallback to NCACN_NP.\n", + domain->name); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + DBG_NOTICE("get_secure_channel_type gave SEC_CHAN_NULL for %s, " + "fallback to noauth on NCACN_NP.\n", + domain->name); + + result = cli_rpc_pipe_open_noauth_transport( + conn->cli, + transport, + &ndr_table_netlogon, + remote_name, + remote_sockaddr, + &conn->netlogon_pipe); + if (!NT_STATUS_IS_OK(result)) { + invalidate_cm_connection(domain); + return result; + } + + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; + } + + result = rpccli_create_netlogon_creds_ctx(creds, + domain->dcname, + msg_ctx, + domain, + &conn->netlogon_creds_ctx); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("rpccli_create_netlogon_creds failed for %s, " + "unable to create NETLOGON credentials: %s\n", + domain->name, nt_errstr(result))); + return result; + } + + result = rpccli_connect_netlogon( + conn->cli, transport, + conn->netlogon_creds_ctx, conn->netlogon_force_reauth, creds, + &conn->netlogon_pipe); + conn->netlogon_force_reauth = false; + if (!NT_STATUS_IS_OK(result)) { + DBG_DEBUG("rpccli_connect_netlogon failed: %s\n", + nt_errstr(result)); + return result; + } + + *cli = conn->netlogon_pipe; + return NT_STATUS_OK; +} + +/**************************************************************************** +Open a NETLOGON connection to a DC, suitable for SamLogon calls. +****************************************************************************/ + +NTSTATUS cm_connect_netlogon(struct winbindd_domain *domain, + struct rpc_pipe_client **cli) +{ + NTSTATUS status; + + status = init_dc_connection_rpc(domain, domain->rodc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (domain->active_directory && domain->can_do_ncacn_ip_tcp) { + status = cm_connect_netlogon_transport(domain, NCACN_IP_TCP, cli); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR) || + NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) { + invalidate_cm_connection(domain); + status = cm_connect_netlogon_transport(domain, NCACN_IP_TCP, cli); + } + if (NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * we tried twice to connect via ncan_ip_tcp and schannel and + * failed - maybe it is a trusted domain we can't connect to ? + * do not try tcp next time - gd + * + * This also prevents LSA over TCP + */ + domain->can_do_ncacn_ip_tcp = false; + } + + status = cm_connect_netlogon_transport(domain, NCACN_NP, cli); + if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) { + /* + * SMB2 session expired, needs reauthentication. Drop + * connection and retry. + */ + invalidate_cm_connection(domain); + status = cm_connect_netlogon_transport(domain, NCACN_NP, cli); + } + + return status; +} + +NTSTATUS cm_connect_netlogon_secure(struct winbindd_domain *domain, + struct rpc_pipe_client **cli, + struct netlogon_creds_cli_context **ppdc) +{ + NTSTATUS status; + + if (domain->secure_channel_type == SEC_CHAN_NULL) { + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + status = cm_connect_netlogon(domain, cli); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (domain->conn.netlogon_creds_ctx == NULL) { + return NT_STATUS_TRUSTED_DOMAIN_FAILURE; + } + + *ppdc = domain->conn.netlogon_creds_ctx; + return NT_STATUS_OK; +} + +void winbind_msg_ip_dropped(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbindd_domain *domain; + char *freeit = NULL; + char *addr; + + if ((data == NULL) + || (data->data == NULL) + || (data->length == 0) + || (data->data[data->length-1] != '\0')) { + DEBUG(1, ("invalid msg_ip_dropped message: not a valid " + "string\n")); + return; + } + + addr = (char *)data->data; + DEBUG(10, ("IP %s dropped\n", addr)); + + if (!is_ipaddress(addr)) { + char *slash; + /* + * Some code sends us ip addresses with the /netmask + * suffix + */ + slash = strchr(addr, '/'); + if (slash == NULL) { + DEBUG(1, ("invalid msg_ip_dropped message: %s\n", + addr)); + return; + } + freeit = talloc_strndup(talloc_tos(), addr, slash-addr); + if (freeit == NULL) { + DEBUG(1, ("talloc failed\n")); + return; + } + addr = freeit; + DEBUG(10, ("Stripped /netmask to IP %s\n", addr)); + } + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + char sockaddr[INET6_ADDRSTRLEN]; + + if (!cli_state_is_connected(domain->conn.cli)) { + continue; + } + + print_sockaddr(sockaddr, sizeof(sockaddr), + smbXcli_conn_local_sockaddr(domain->conn.cli->conn)); + + if (strequal(sockaddr, addr)) { + smbXcli_conn_disconnect(domain->conn.cli->conn, NT_STATUS_OK); + } + } + TALLOC_FREE(freeit); +} + +void winbind_msg_disconnect_dc(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbindd_domain *domain; + + for (domain = domain_list(); domain; domain = domain->next) { + if (domain->internal) { + continue; + } + invalidate_cm_connection(domain); + } +} diff --git a/source3/winbindd/winbindd_cred_cache.c b/source3/winbindd/winbindd_cred_cache.c new file mode 100644 index 0000000..59daaff --- /dev/null +++ b/source3/winbindd/winbindd_cred_cache.c @@ -0,0 +1,1061 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - krb5 credential cache functions + and in-memory cache functions. + + Copyright (C) Guenther Deschner 2005-2006 + Copyright (C) Jeremy Allison 2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "winbindd.h" +#include "../libcli/auth/libcli_auth.h" +#include "smb_krb5.h" +#include "libads/kerberos_proto.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* uncomment this to do fast debugging on the krb5 ticket renewal event */ +#ifdef DEBUG_KRB5_TKT_RENEWAL +#undef DEBUG_KRB5_TKT_RENEWAL +#endif + +#define MAX_CCACHES 100 + +static struct WINBINDD_CCACHE_ENTRY *ccache_list; +static void krb5_ticket_gain_handler(struct tevent_context *, + struct tevent_timer *, + struct timeval, + void *); +static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *, + struct timeval); + +/* The Krb5 ticket refresh handler should be scheduled + at one-half of the period from now till the tkt + expiration */ + +static time_t krb5_event_refresh_time(time_t end_time) +{ + time_t rest = end_time - time(NULL); + return end_time - rest/2; +} + +/**************************************************************** + Find an entry by name. +****************************************************************/ + +static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username) +{ + struct WINBINDD_CCACHE_ENTRY *entry; + + for (entry = ccache_list; entry; entry = entry->next) { + if (strequal(entry->username, username)) { + return entry; + } + } + return NULL; +} + +/**************************************************************** + How many do we have ? +****************************************************************/ + +static int ccache_entry_count(void) +{ + struct WINBINDD_CCACHE_ENTRY *entry; + int i = 0; + + for (entry = ccache_list; entry; entry = entry->next) { + i++; + } + return i; +} + +void ccache_remove_all_after_fork(void) +{ + struct WINBINDD_CCACHE_ENTRY *cur, *next; + + for (cur = ccache_list; cur; cur = next) { + next = cur->next; + DLIST_REMOVE(ccache_list, cur); + TALLOC_FREE(cur->event); + TALLOC_FREE(cur); + } + + return; +} + +/**************************************************************** + Do the work of refreshing the ticket. +****************************************************************/ + +static void krb5_ticket_refresh_handler(struct tevent_context *event_ctx, + struct tevent_timer *te, + struct timeval now, + void *private_data) +{ + struct WINBINDD_CCACHE_ENTRY *entry = + talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY); +#ifdef HAVE_KRB5 + int ret; + time_t new_start; + time_t expire_time = 0; + struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr; +#endif + + DBG_DEBUG("event called for: %s, %s\n", + entry->ccname, entry->username); + + TALLOC_FREE(entry->event); + +#ifdef HAVE_KRB5 + + /* Kinit again if we have the user password and we can't renew the old + * tgt anymore + * NB + * This happens when machine are put to sleep for a very long time. */ + + if (entry->renew_until < time(NULL)) { +rekinit: + if (cred_ptr && cred_ptr->pass) { + + set_effective_uid(entry->uid); + + ret = kerberos_kinit_password_ext(entry->principal_name, + cred_ptr->pass, + 0, /* hm, can we do time correction here ? */ + &entry->refresh_time, + &entry->renew_until, + entry->ccname, + False, /* no PAC required anymore */ + True, + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME, + NULL, + NULL, + NULL, + NULL); + gain_root_privilege(); + + if (ret) { + DEBUG(3,("krb5_ticket_refresh_handler: " + "could not re-kinit: %s\n", + error_message(ret))); + /* destroy the ticket because we cannot rekinit + * it, ignore error here */ + ads_kdestroy(entry->ccname); + + /* Don't break the ticket refresh chain: retry + * refreshing ticket sometime later when KDC is + * unreachable -- BoYang. More error code handling + * here? + * */ + + if ((ret == KRB5_KDC_UNREACH) + || (ret == KRB5_REALM_CANT_RESOLVE)) { +#if defined(DEBUG_KRB5_TKT_RENEWAL) + new_start = time(NULL) + 30; +#else + new_start = time(NULL) + + MAX(30, lp_winbind_cache_time()); +#endif + add_krb5_ticket_gain_handler_event(entry, + timeval_set(new_start, 0)); + return; + } + TALLOC_FREE(entry->event); + return; + } + + DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit " + "for: %s in ccache: %s\n", + entry->principal_name, entry->ccname)); + +#if defined(DEBUG_KRB5_TKT_RENEWAL) + new_start = time(NULL) + 30; +#else + /* The tkt should be refreshed at one-half the period + from now to the expiration time */ + expire_time = entry->refresh_time; + new_start = krb5_event_refresh_time(entry->refresh_time); +#endif + goto done; + } else { + /* can this happen? + * No cached credentials + * destroy ticket and refresh chain + * */ + ads_kdestroy(entry->ccname); + TALLOC_FREE(entry->event); + return; + } + } + + set_effective_uid(entry->uid); + + ret = smb_krb5_renew_ticket(entry->ccname, + entry->canon_principal, + entry->service, + &new_start); +#if defined(DEBUG_KRB5_TKT_RENEWAL) + new_start = time(NULL) + 30; +#else + expire_time = new_start; + new_start = krb5_event_refresh_time(new_start); +#endif + + gain_root_privilege(); + + if (ret) { + DEBUG(3,("krb5_ticket_refresh_handler: " + "could not renew tickets: %s\n", + error_message(ret))); + /* maybe we are beyond the renewing window */ + + /* evil rises here, we refresh ticket failed, + * but the ticket might be expired. Therefore, + * When we refresh ticket failed, destroy the + * ticket */ + + ads_kdestroy(entry->ccname); + + /* avoid breaking the renewal chain: retry in + * lp_winbind_cache_time() seconds when the KDC was not + * available right now. + * the return code can be KRB5_REALM_CANT_RESOLVE. + * More error code handling here? */ + + if ((ret == KRB5_KDC_UNREACH) + || (ret == KRB5_REALM_CANT_RESOLVE)) { +#if defined(DEBUG_KRB5_TKT_RENEWAL) + new_start = time(NULL) + 30; +#else + new_start = time(NULL) + + MAX(30, lp_winbind_cache_time()); +#endif + /* ticket is destroyed here, we have to regain it + * if it is possible */ + add_krb5_ticket_gain_handler_event(entry, + timeval_set(new_start, 0)); + return; + } + + /* This is evil, if the ticket was already expired. + * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED. + * But there is still a chance that we can rekinit it. + * + * This happens when user login in online mode, and then network + * down or something cause winbind goes offline for a very long time, + * and then goes online again. ticket expired, renew failed. + * This happens when machine are put to sleep for a long time, + * but shorter than entry->renew_util. + * NB + * Looks like the KDC is reachable, we want to rekinit as soon as + * possible instead of waiting some time later. */ + if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED) + || (ret == KRB5_FCC_NOFILE)) goto rekinit; + + return; + } + +done: + /* in cases that ticket will be unrenewable soon, we don't try to renew ticket + * but try to regain ticket if it is possible */ + if (entry->renew_until && expire_time + && (entry->renew_until <= expire_time)) { + /* try to regain ticket 10 seconds before expiration */ + expire_time -= 10; + add_krb5_ticket_gain_handler_event(entry, + timeval_set(expire_time, 0)); + return; + } + + if (entry->refresh_time == 0) { + entry->refresh_time = new_start; + } + entry->event = tevent_add_timer(global_event_context(), entry, + timeval_set(new_start, 0), + krb5_ticket_refresh_handler, + entry); + +#endif +} + +/**************************************************************** + Do the work of regaining a ticket when coming from offline auth. +****************************************************************/ + +static void krb5_ticket_gain_handler(struct tevent_context *event_ctx, + struct tevent_timer *te, + struct timeval now, + void *private_data) +{ + struct WINBINDD_CCACHE_ENTRY *entry = + talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY); +#ifdef HAVE_KRB5 + int ret; + struct timeval t; + struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr; + struct winbindd_domain *domain = NULL; +#endif + + DBG_DEBUG("event called for: %s, %s\n", + entry->ccname, entry->username); + + TALLOC_FREE(entry->event); + +#ifdef HAVE_KRB5 + + if (!cred_ptr || !cred_ptr->pass) { + DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n")); + return; + } + + if ((domain = find_domain_from_name(entry->realm)) == NULL) { + DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n")); + return; + } + + if (!domain->online) { + goto retry_later; + } + + set_effective_uid(entry->uid); + + ret = kerberos_kinit_password_ext(entry->principal_name, + cred_ptr->pass, + 0, /* hm, can we do time correction here ? */ + &entry->refresh_time, + &entry->renew_until, + entry->ccname, + False, /* no PAC required anymore */ + True, + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME, + NULL, + NULL, + NULL, + NULL); + gain_root_privilege(); + + if (ret) { + DEBUG(3,("krb5_ticket_gain_handler: " + "could not kinit: %s\n", + error_message(ret))); + /* evil. If we cannot do it, destroy any the __maybe__ + * __existing__ ticket */ + ads_kdestroy(entry->ccname); + goto retry_later; + } + + DEBUG(10,("krb5_ticket_gain_handler: " + "successful kinit for: %s in ccache: %s\n", + entry->principal_name, entry->ccname)); + + goto got_ticket; + + retry_later: + +#if defined(DEBUG_KRB5_TKT_RENEWAL) + t = timeval_set(time(NULL) + 30, 0); +#else + t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0); +#endif + + add_krb5_ticket_gain_handler_event(entry, t); + return; + + got_ticket: + +#if defined(DEBUG_KRB5_TKT_RENEWAL) + t = timeval_set(time(NULL) + 30, 0); +#else + t = timeval_set(krb5_event_refresh_time(entry->refresh_time), 0); +#endif + + if (entry->refresh_time == 0) { + entry->refresh_time = t.tv_sec; + } + entry->event = tevent_add_timer(global_event_context(), + entry, + t, + krb5_ticket_refresh_handler, + entry); + + return; +#endif +} + +/************************************************************** + The gain initial ticket case is recognised as entry->refresh_time + is always zero. +**************************************************************/ + +static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry, + struct timeval t) +{ + entry->refresh_time = 0; + entry->event = tevent_add_timer(global_event_context(), + entry, + t, + krb5_ticket_gain_handler, + entry); +} + +void ccache_regain_all_now(void) +{ + struct WINBINDD_CCACHE_ENTRY *cur; + struct timeval t = timeval_current(); + + for (cur = ccache_list; cur; cur = cur->next) { + struct tevent_timer *new_event; + + /* + * if refresh_time is 0, we know that the + * the event has the krb5_ticket_gain_handler + */ + if (cur->refresh_time == 0) { + new_event = tevent_add_timer(global_event_context(), + cur, + t, + krb5_ticket_gain_handler, + cur); + } else { + new_event = tevent_add_timer(global_event_context(), + cur, + t, + krb5_ticket_refresh_handler, + cur); + } + + if (!new_event) { + continue; + } + + TALLOC_FREE(cur->event); + cur->event = new_event; + } + + return; +} + +/**************************************************************** + Check if an ccache entry exists. +****************************************************************/ + +bool ccache_entry_exists(const char *username) +{ + struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username); + return (entry != NULL); +} + +/**************************************************************** + Ensure we're changing the correct entry. +****************************************************************/ + +bool ccache_entry_identical(const char *username, + uid_t uid, + const char *ccname) +{ + struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username); + + if (!entry) { + return False; + } + + if (entry->uid != uid) { + DEBUG(0,("cache_entry_identical: uid's differ: %u != %u\n", + (unsigned int)entry->uid, (unsigned int)uid)); + return False; + } + if (!strcsequal(entry->ccname, ccname)) { + DEBUG(0,("cache_entry_identical: " + "ccnames differ: (cache) %s != (client) %s\n", + entry->ccname, ccname)); + return False; + } + return True; +} + +NTSTATUS add_ccache_to_list(const char *princ_name, + const char *ccname, + const char *username, + const char *pass, + const char *realm, + uid_t uid, + time_t create_time, + time_t ticket_end, + time_t renew_until, + bool postponed_request, + const char *canon_principal, + const char *canon_realm) +{ + struct WINBINDD_CCACHE_ENTRY *entry = NULL; + struct timeval t; + NTSTATUS ntret; + + if ((username == NULL && princ_name == NULL) || + ccname == NULL || uid == (uid_t)-1) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (ccache_entry_count() + 1 > MAX_CCACHES) { + DEBUG(10,("add_ccache_to_list: " + "max number of ccaches reached\n")); + return NT_STATUS_NO_MORE_ENTRIES; + } + + /* Reference count old entries */ + entry = get_ccache_by_username(username); + if (entry) { + /* Check cached entries are identical. */ + if (!ccache_entry_identical(username, uid, ccname)) { + return NT_STATUS_INVALID_PARAMETER; + } + entry->ref_count++; + DEBUG(10,("add_ccache_to_list: " + "ref count on entry %s is now %d\n", + username, entry->ref_count)); + /* FIXME: in this case we still might want to have a krb5 cred + * event handler created - gd + * Add ticket refresh handler here */ + + if (!lp_winbind_refresh_tickets() || renew_until <= 0) { + return NT_STATUS_OK; + } + + if (!entry->event) { + if (postponed_request) { + t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0); + add_krb5_ticket_gain_handler_event(entry, t); + } else { + /* Renew at 1/2 the ticket expiration time */ +#if defined(DEBUG_KRB5_TKT_RENEWAL) + t = timeval_set(time(NULL)+30, 0); +#else + t = timeval_set(krb5_event_refresh_time(ticket_end), + 0); +#endif + if (!entry->refresh_time) { + entry->refresh_time = t.tv_sec; + } + entry->event = tevent_add_timer(global_event_context(), + entry, + t, + krb5_ticket_refresh_handler, + entry); + } + + if (!entry->event) { + ntret = remove_ccache(username); + if (!NT_STATUS_IS_OK(ntret)) { + DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 " + "ccache %s for user %s\n", entry->ccname, + entry->username)); + DEBUG(0, ("add_ccache_to_list: error is %s\n", + nt_errstr(ntret))); + return ntret; + } + return NT_STATUS_NO_MEMORY; + } + + DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n")); + + } + + /* + * If we're set up to renew our krb5 tickets, we must + * cache the credentials in memory for the ticket + * renew function (or increase the reference count + * if we're logging in more than once). Fix inspired + * by patch from Ian Gordon <ian.gordon@strath.ac.uk> + * for bugid #9098. + */ + + ntret = winbindd_add_memory_creds(username, uid, pass); + DEBUG(10, ("winbindd_add_memory_creds returned: %s\n", + nt_errstr(ntret))); + + return NT_STATUS_OK; + } + + entry = talloc(NULL, struct WINBINDD_CCACHE_ENTRY); + if (!entry) { + return NT_STATUS_NO_MEMORY; + } + + ZERO_STRUCTP(entry); + + if (username) { + entry->username = talloc_strdup(entry, username); + if (!entry->username) { + goto no_mem; + } + } + if (princ_name) { + entry->principal_name = talloc_strdup(entry, princ_name); + if (!entry->principal_name) { + goto no_mem; + } + } + if (canon_principal != NULL) { + entry->canon_principal = talloc_strdup(entry, canon_principal); + if (entry->canon_principal == NULL) { + goto no_mem; + } + } + if (canon_realm != NULL) { + entry->canon_realm = talloc_strdup(entry, canon_realm); + if (entry->canon_realm == NULL) { + goto no_mem; + } + } + + entry->ccname = talloc_strdup(entry, ccname); + if (!entry->ccname) { + goto no_mem; + } + + entry->realm = talloc_strdup(entry, realm); + if (!entry->realm) { + goto no_mem; + } + + entry->service = talloc_asprintf(entry, + "%s/%s@%s", + KRB5_TGS_NAME, + canon_realm, + canon_realm); + if (entry->service == NULL) { + goto no_mem; + } + + entry->create_time = create_time; + entry->renew_until = renew_until; + entry->uid = uid; + entry->ref_count = 1; + + if (!lp_winbind_refresh_tickets() || renew_until <= 0) { + goto add_entry; + } + + if (postponed_request) { + t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0); + add_krb5_ticket_gain_handler_event(entry, t); + } else { + /* Renew at 1/2 the ticket expiration time */ +#if defined(DEBUG_KRB5_TKT_RENEWAL) + t = timeval_set(time(NULL)+30, 0); +#else + t = timeval_set(krb5_event_refresh_time(ticket_end), 0); +#endif + if (entry->refresh_time == 0) { + entry->refresh_time = t.tv_sec; + } + entry->event = tevent_add_timer(global_event_context(), + entry, + t, + krb5_ticket_refresh_handler, + entry); + } + + if (!entry->event) { + goto no_mem; + } + + DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n")); + + add_entry: + + DLIST_ADD(ccache_list, entry); + + DBG_DEBUG("Added ccache [%s] for user [%s] and service [%s]\n", + entry->ccname, entry->username, entry->service); + + if (entry->event) { + /* + * If we're set up to renew our krb5 tickets, we must + * cache the credentials in memory for the ticket + * renew function. Fix inspired by patch from + * Ian Gordon <ian.gordon@strath.ac.uk> for + * bugid #9098. + */ + + ntret = winbindd_add_memory_creds(username, uid, pass); + DEBUG(10, ("winbindd_add_memory_creds returned: %s\n", + nt_errstr(ntret))); + } + + return NT_STATUS_OK; + + no_mem: + + TALLOC_FREE(entry); + return NT_STATUS_NO_MEMORY; +} + +/******************************************************************* + Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer + referenced. + *******************************************************************/ + +NTSTATUS remove_ccache(const char *username) +{ + struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username); + NTSTATUS status = NT_STATUS_OK; +#ifdef HAVE_KRB5 + krb5_error_code ret; +#endif + + if (!entry) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (entry->ref_count <= 0) { + DEBUG(0,("remove_ccache: logic error. " + "ref count for user %s = %d\n", + username, entry->ref_count)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + entry->ref_count--; + + if (entry->ref_count > 0) { + DEBUG(10,("remove_ccache: entry %s ref count now %d\n", + username, entry->ref_count)); + return NT_STATUS_OK; + } + + /* no references any more */ + + DLIST_REMOVE(ccache_list, entry); + TALLOC_FREE(entry->event); /* unregisters events */ + +#ifdef HAVE_KRB5 + ret = ads_kdestroy(entry->ccname); + + /* we ignore the error when there has been no credential cache */ + if (ret == KRB5_FCC_NOFILE) { + ret = 0; + } else if (ret) { + DEBUG(0,("remove_ccache: " + "failed to destroy user krb5 ccache %s with: %s\n", + entry->ccname, error_message(ret))); + } else { + DEBUG(10,("remove_ccache: " + "successfully destroyed krb5 ccache %s for user %s\n", + entry->ccname, username)); + } + status = krb5_to_nt_status(ret); +#endif + + TALLOC_FREE(entry); + DEBUG(10,("remove_ccache: removed ccache for user %s\n", username)); + + return status; +} + +/******************************************************************* + In memory credentials cache code. +*******************************************************************/ + +static struct WINBINDD_MEMORY_CREDS *memory_creds_list; + +/*********************************************************** + Find an entry on the list by name. +***********************************************************/ + +struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username) +{ + struct WINBINDD_MEMORY_CREDS *p; + + for (p = memory_creds_list; p; p = p->next) { + if (strequal(p->username, username)) { + return p; + } + } + return NULL; +} + +/*********************************************************** + Store the required creds and mlock them. +***********************************************************/ + +static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp, + const char *pass) +{ +#if !defined(HAVE_MLOCK) + return NT_STATUS_OK; +#else + /* new_entry->nt_hash is the base pointer for the block + of memory pointed into by new_entry->lm_hash and + new_entry->pass (if we're storing plaintext). */ + + memcredp->len = NT_HASH_LEN + LM_HASH_LEN; + if (pass) { + memcredp->len += strlen(pass)+1; + } + + +#if defined(LINUX) + /* aligning the memory on on x86_64 and compiling + with gcc 4.1 using -O2 causes a segv in the + next memset() --jerry */ + memcredp->nt_hash = SMB_MALLOC_ARRAY(unsigned char, memcredp->len); +#else + /* On non-linux platforms, mlock()'d memory must be aligned */ + memcredp->nt_hash = SMB_MEMALIGN_ARRAY(unsigned char, + getpagesize(), memcredp->len); +#endif + if (!memcredp->nt_hash) { + return NT_STATUS_NO_MEMORY; + } + memset(memcredp->nt_hash, 0x0, memcredp->len); + + memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN; + +#ifdef DEBUG_PASSWORD + DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash)); +#endif + if ((mlock(memcredp->nt_hash, memcredp->len)) == -1) { + DEBUG(0,("failed to mlock memory: %s (%d)\n", + strerror(errno), errno)); + SAFE_FREE(memcredp->nt_hash); + return map_nt_error_from_unix(errno); + } + +#ifdef DEBUG_PASSWORD + DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash)); +#endif + + if (pass) { + /* Create and store the password hashes. */ + E_md4hash(pass, memcredp->nt_hash); + E_deshash(pass, memcredp->lm_hash); + + memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN; + memcpy(memcredp->pass, pass, + memcredp->len - NT_HASH_LEN - LM_HASH_LEN); + } + + return NT_STATUS_OK; +#endif +} + +/*********************************************************** + Destroy existing creds. +***********************************************************/ + +static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp) +{ +#if !defined(HAVE_MUNLOCK) + return NT_STATUS_OK; +#else + if (munlock(memcredp->nt_hash, memcredp->len) == -1) { + DEBUG(0,("failed to munlock memory: %s (%d)\n", + strerror(errno), errno)); + return map_nt_error_from_unix(errno); + } + memset(memcredp->nt_hash, '\0', memcredp->len); + SAFE_FREE(memcredp->nt_hash); + memcredp->nt_hash = NULL; + memcredp->lm_hash = NULL; + memcredp->pass = NULL; + memcredp->len = 0; + return NT_STATUS_OK; +#endif +} + +/*********************************************************** + Replace the required creds with new ones (password change). +***********************************************************/ + +static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp, + const char *pass) +{ + NTSTATUS status = delete_memory_creds(memcredp); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return store_memory_creds(memcredp, pass); +} + +/************************************************************* + Store credentials in memory in a list. +*************************************************************/ + +static NTSTATUS winbindd_add_memory_creds_internal(const char *username, + uid_t uid, + const char *pass) +{ + /* Shortcut to ensure we don't store if no mlock. */ +#if !defined(HAVE_MLOCK) || !defined(HAVE_MUNLOCK) + return NT_STATUS_OK; +#else + NTSTATUS status; + struct WINBINDD_MEMORY_CREDS *memcredp = NULL; + + memcredp = find_memory_creds_by_name(username); + if (uid == (uid_t)-1) { + DEBUG(0,("winbindd_add_memory_creds_internal: " + "invalid uid for user %s.\n", username)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (memcredp) { + /* Already exists. Increment the reference count and replace stored creds. */ + if (uid != memcredp->uid) { + DEBUG(0,("winbindd_add_memory_creds_internal: " + "uid %u for user %s doesn't " + "match stored uid %u. Replacing.\n", + (unsigned int)uid, username, + (unsigned int)memcredp->uid)); + memcredp->uid = uid; + } + memcredp->ref_count++; + DEBUG(10,("winbindd_add_memory_creds_internal: " + "ref count for user %s is now %d\n", + username, memcredp->ref_count)); + return winbindd_replace_memory_creds_internal(memcredp, pass); + } + + memcredp = talloc_zero(NULL, struct WINBINDD_MEMORY_CREDS); + if (!memcredp) { + return NT_STATUS_NO_MEMORY; + } + memcredp->username = talloc_strdup(memcredp, username); + if (!memcredp->username) { + talloc_destroy(memcredp); + return NT_STATUS_NO_MEMORY; + } + + status = store_memory_creds(memcredp, pass); + if (!NT_STATUS_IS_OK(status)) { + talloc_destroy(memcredp); + return status; + } + + memcredp->uid = uid; + memcredp->ref_count = 1; + DLIST_ADD(memory_creds_list, memcredp); + + DEBUG(10,("winbindd_add_memory_creds_internal: " + "added entry for user %s\n", username)); + + return NT_STATUS_OK; +#endif +} + +/************************************************************* + Store users credentials in memory. If we also have a + struct WINBINDD_CCACHE_ENTRY for this username with a + refresh timer, then store the plaintext of the password + and associate the new credentials with the struct WINBINDD_CCACHE_ENTRY. +*************************************************************/ + +NTSTATUS winbindd_add_memory_creds(const char *username, + uid_t uid, + const char *pass) +{ + struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username); + NTSTATUS status; + + status = winbindd_add_memory_creds_internal(username, uid, pass); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (entry) { + struct WINBINDD_MEMORY_CREDS *memcredp = NULL; + memcredp = find_memory_creds_by_name(username); + if (memcredp) { + entry->cred_ptr = memcredp; + } + } + + return status; +} + +/************************************************************* + Decrement the in-memory ref count - delete if zero. +*************************************************************/ + +NTSTATUS winbindd_delete_memory_creds(const char *username) +{ + struct WINBINDD_MEMORY_CREDS *memcredp = NULL; + struct WINBINDD_CCACHE_ENTRY *entry = NULL; + NTSTATUS status = NT_STATUS_OK; + + memcredp = find_memory_creds_by_name(username); + entry = get_ccache_by_username(username); + + if (!memcredp) { + DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n", + username)); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (memcredp->ref_count <= 0) { + DEBUG(0,("winbindd_delete_memory_creds: logic error. " + "ref count for user %s = %d\n", + username, memcredp->ref_count)); + status = NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memcredp->ref_count--; + if (memcredp->ref_count <= 0) { + delete_memory_creds(memcredp); + DLIST_REMOVE(memory_creds_list, memcredp); + talloc_destroy(memcredp); + DEBUG(10,("winbindd_delete_memory_creds: " + "deleted entry for user %s\n", + username)); + } else { + DEBUG(10,("winbindd_delete_memory_creds: " + "entry for user %s ref_count now %d\n", + username, memcredp->ref_count)); + } + + if (entry) { + /* Ensure we have no dangling references to this. */ + entry->cred_ptr = NULL; + } + + return status; +} + +/*********************************************************** + Replace the required creds with new ones (password change). +***********************************************************/ + +NTSTATUS winbindd_replace_memory_creds(const char *username, + const char *pass) +{ + struct WINBINDD_MEMORY_CREDS *memcredp = NULL; + + memcredp = find_memory_creds_by_name(username); + if (!memcredp) { + DEBUG(10,("winbindd_replace_memory_creds: unknown user %s\n", + username)); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n", + username)); + + return winbindd_replace_memory_creds_internal(memcredp, pass); +} diff --git a/source3/winbindd/winbindd_creds.c b/source3/winbindd/winbindd_creds.c new file mode 100644 index 0000000..a0cce7e --- /dev/null +++ b/source3/winbindd/winbindd_creds.c @@ -0,0 +1,147 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - cached credentials functions + + Copyright (C) Guenther Deschner 2005 + + 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.h" +#include "../libcli/auth/libcli_auth.h" +#include "../libcli/security/security.h" +#include "libsmb/samlogon_cache.h" +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +#define MAX_CACHED_LOGINS 10 + +NTSTATUS winbindd_get_creds(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + struct netr_SamInfo3 **info3, + const uint8_t **cached_nt_pass, + const uint8_t **cred_salt) +{ + struct netr_SamInfo3 *info; + NTSTATUS status; + + status = wcache_get_creds(domain, mem_ctx, sid, cached_nt_pass, cred_salt); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + info = netsamlogon_cache_get(mem_ctx, sid); + if (info == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + *info3 = info; + + return NT_STATUS_OK; +} + + +NTSTATUS winbindd_store_creds(struct winbindd_domain *domain, + const char *user, + const char *pass, + struct netr_SamInfo3 *info3) +{ + NTSTATUS status; + uchar nt_pass[NT_HASH_LEN]; + struct dom_sid cred_sid; + + if (info3 != NULL) { + + sid_compose(&cred_sid, info3->base.domain_sid, + info3->base.rid); + info3->base.user_flags |= NETLOGON_CACHED_ACCOUNT; + + } else if (user != NULL) { + + /* do lookup ourself */ + + enum lsa_SidType type; + + if (!lookup_cached_name(domain->name, /* namespace */ + domain->name, + user, + &cred_sid, + &type)) { + return NT_STATUS_NO_SUCH_USER; + } + } else { + return NT_STATUS_INVALID_PARAMETER; + } + + if (pass) { + + int count = 0; + + status = wcache_count_cached_creds(domain, &count); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(11,("we have %d cached creds\n", count)); + + if (count + 1 > MAX_CACHED_LOGINS) { + + DEBUG(10,("need to delete the oldest cached login\n")); + + status = wcache_remove_oldest_cached_creds(domain, &cred_sid); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("failed to remove oldest cached cred: %s\n", + nt_errstr(status))); + return status; + } + } + + E_md4hash(pass, nt_pass); + + dump_data_pw("nt_pass", nt_pass, NT_HASH_LEN); + + status = wcache_save_creds(domain, &cred_sid, nt_pass); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (info3 != NULL && user != NULL) { + if (!netsamlogon_cache_store(user, info3)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + return NT_STATUS_OK; +} + +NTSTATUS winbindd_update_creds_by_info3(struct winbindd_domain *domain, + const char *user, + const char *pass, + struct netr_SamInfo3 *info3) +{ + return winbindd_store_creds(domain, user, pass, info3); +} + +NTSTATUS winbindd_update_creds_by_name(struct winbindd_domain *domain, + const char *user, + const char *pass) +{ + return winbindd_store_creds(domain, user, pass, NULL); +} + + diff --git a/source3/winbindd/winbindd_domain.c b/source3/winbindd/winbindd_domain.c new file mode 100644 index 0000000..4c8aa9a --- /dev/null +++ b/source3/winbindd/winbindd_domain.c @@ -0,0 +1,36 @@ +/* + Unix SMB/CIFS implementation. + + Winbind domain child functions + + Copyright (C) Stefan Metzmacher 2007 + + 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.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +void setup_domain_child(struct winbindd_domain *domain) +{ + int i; + + for (i=0; i<talloc_array_length(domain->children); i++) { + setup_child(domain, &domain->children[i], + "log.wb", domain->name); + } +} diff --git a/source3/winbindd/winbindd_domain_info.c b/source3/winbindd/winbindd_domain_info.c new file mode 100644 index 0000000..c4364d9 --- /dev/null +++ b/source3/winbindd/winbindd_domain_info.c @@ -0,0 +1,141 @@ +/* + * Unix SMB/CIFS implementation. + * async implementation of WINBINDD_DOMAIN_INFO + * Copyright (C) Volker Lendecke 2018 + * + * 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.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_domain_info_state { + struct winbindd_domain *domain; + uint32_t in; + uint32_t out; +}; + +static void winbindd_domain_info_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_domain_info_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_domain_info_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_domain_info_state); + if (req == NULL) { + return NULL; + } + + DEBUG(3, ("[%5lu]: domain_info [%s]\n", (unsigned long)cli->pid, + cli->request->domain_name)); + + state->domain = find_domain_from_name_noinit( + cli->request->domain_name); + + if (state->domain == NULL) { + DEBUG(3, ("Did not find domain [%s]\n", + cli->request->domain_name)); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + + if (state->domain->initialized) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + /* + * Send a ping down. This implicitly initializes the domain. + */ + + state->in = cli->pid; + state->out = 0; + subreq = dcerpc_wbint_Ping_send(state, + global_event_context(), + dom_child_handle(state->domain), + state->in, + &state->out); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_domain_info_done, req); + + return req; +} + +static void winbindd_domain_info_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_domain_info_state *state = tevent_req_data( + req, struct winbindd_domain_info_state); + NTSTATUS status, result; + + status = dcerpc_wbint_Ping_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + DBG_NOTICE("dcerpc_wbint_Ping call failed: %s\n", + nt_errstr(status)); + return; + } + + if (tevent_req_nterror(req, result)) { + DBG_NOTICE("dcerpc_wbint_Ping failed: %s\n", + nt_errstr(result)); + return; + } + + if (!state->domain->initialized) { + DBG_INFO("dcerpc_wbint_Ping did not initialize domain %s\n", + state->domain->name); + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + tevent_req_done(req); +} + +NTSTATUS winbindd_domain_info_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_domain_info_state *state = tevent_req_data( + req, struct winbindd_domain_info_state); + struct winbindd_domain *domain = state->domain; + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + DBG_NOTICE("winbindd_domain_info failed: %s\n", + nt_errstr(status)); + return status; + } + + fstrcpy(response->data.domain_info.name, domain->name); + fstrcpy(response->data.domain_info.alt_name, domain->alt_name); + sid_to_fstring(response->data.domain_info.sid, &domain->sid); + + response->data.domain_info.native_mode = domain->native_mode; + response->data.domain_info.active_directory = domain->active_directory; + response->data.domain_info.primary = domain->primary; + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_dsgetdcname.c b/source3/winbindd/winbindd_dsgetdcname.c new file mode 100644 index 0000000..9dadeab --- /dev/null +++ b/source3/winbindd/winbindd_dsgetdcname.c @@ -0,0 +1,200 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_DSGETDCNAME + Copyright (C) Volker Lendecke 2009 + + 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 "util/debug.h" +#include "winbindd.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "lib/util/string_wrappers.h" + +struct winbindd_dsgetdcname_state { + struct GUID guid; + struct netr_DsRGetDCNameInfo *dc_info; +}; + +static uint32_t get_dsgetdc_flags(uint32_t wbc_flags); +static void winbindd_dsgetdcname_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_dsgetdcname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct dcerpc_binding_handle *child_binding_handle = NULL; + struct winbindd_dsgetdcname_state *state; + struct GUID *guid_ptr = NULL; + uint32_t ds_flags = 0; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_dsgetdcname_state); + if (req == NULL) { + return NULL; + } + + D_NOTICE("[%s (%u)] Winbind external command DSGETDCNAME start.\n", + cli->client_name, + (unsigned int)cli->pid); + + request->data.dsgetdcname.domain_name + [sizeof(request->data.dsgetdcname.domain_name)-1] = '\0'; + request->data.dsgetdcname.site_name + [sizeof(request->data.dsgetdcname.site_name)-1] = '\0'; + request->data.dsgetdcname.domain_guid + [sizeof(request->data.dsgetdcname.domain_guid)-1] = '\0'; + + D_NOTICE("Calling DsGetDcName for domain '%s'.\n", + request->data.dsgetdcname.domain_name); + + ds_flags = get_dsgetdc_flags(request->data.dsgetdcname.flags); + + status = GUID_from_string(request->data.dsgetdcname.domain_guid, + &state->guid); + if (NT_STATUS_IS_OK(status) && !GUID_all_zero(&state->guid)) { + guid_ptr = &state->guid; + } + + child_binding_handle = locator_child_handle(); + + subreq = dcerpc_wbint_DsGetDcName_send( + state, ev, child_binding_handle, + request->data.dsgetdcname.domain_name, guid_ptr, + request->data.dsgetdcname.site_name, + ds_flags, &state->dc_info); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_dsgetdcname_done, req); + return req; +} + +static void winbindd_dsgetdcname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_dsgetdcname_state *state = tevent_req_data( + req, struct winbindd_dsgetdcname_state); + NTSTATUS status, result; + + status = dcerpc_wbint_DsGetDcName_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_dsgetdcname_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_dsgetdcname_state *state = tevent_req_data( + req, struct winbindd_dsgetdcname_state); + struct GUID_txt_buf guid_str_buf; + char *guid_str; + NTSTATUS status; + + D_NOTICE("Winbind external command DSGETDCNAME end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with: %s\n", nt_errstr(status)); + return status; + } + + fstrcpy(response->data.dsgetdcname.dc_unc, + state->dc_info->dc_unc); + fstrcpy(response->data.dsgetdcname.dc_address, + state->dc_info->dc_address); + response->data.dsgetdcname.dc_address_type = + state->dc_info->dc_address_type; + + guid_str = GUID_buf_string(&state->dc_info->domain_guid, + &guid_str_buf); + fstrcpy(response->data.dsgetdcname.domain_guid, guid_str); + + fstrcpy(response->data.dsgetdcname.domain_name, + state->dc_info->domain_name); + fstrcpy(response->data.dsgetdcname.forest_name, + state->dc_info->forest_name); + response->data.dsgetdcname.dc_flags = state->dc_info->dc_flags; + fstrcpy(response->data.dsgetdcname.dc_site_name, + state->dc_info->dc_site_name); + fstrcpy(response->data.dsgetdcname.client_site_name, + state->dc_info->client_site_name); + + return NT_STATUS_OK; +} + +static uint32_t get_dsgetdc_flags(uint32_t wbc_flags) +{ + struct wbc_flag_map { + uint32_t wbc_dc_flag; + uint32_t ds_dc_flags; + } lookup_dc_flags[] = { + { WBC_LOOKUP_DC_FORCE_REDISCOVERY, + DS_FORCE_REDISCOVERY }, + { WBC_LOOKUP_DC_DS_REQUIRED, + DS_DIRECTORY_SERVICE_REQUIRED }, + { WBC_LOOKUP_DC_DS_PREFERRED, + DS_DIRECTORY_SERVICE_PREFERRED}, + { WBC_LOOKUP_DC_GC_SERVER_REQUIRED, + DS_GC_SERVER_REQUIRED }, + { WBC_LOOKUP_DC_PDC_REQUIRED, + DS_PDC_REQUIRED}, + { WBC_LOOKUP_DC_BACKGROUND_ONLY, + DS_BACKGROUND_ONLY }, + { WBC_LOOKUP_DC_IP_REQUIRED, + DS_IP_REQUIRED }, + { WBC_LOOKUP_DC_KDC_REQUIRED, + DS_KDC_REQUIRED }, + { WBC_LOOKUP_DC_TIMESERV_REQUIRED, + DS_TIMESERV_REQUIRED }, + { WBC_LOOKUP_DC_WRITABLE_REQUIRED, + DS_WRITABLE_REQUIRED }, + { WBC_LOOKUP_DC_GOOD_TIMESERV_PREFERRED, + DS_GOOD_TIMESERV_PREFERRED }, + { WBC_LOOKUP_DC_AVOID_SELF, + DS_AVOID_SELF }, + { WBC_LOOKUP_DC_ONLY_LDAP_NEEDED, + DS_ONLY_LDAP_NEEDED }, + { WBC_LOOKUP_DC_IS_FLAT_NAME, + DS_IS_FLAT_NAME }, + { WBC_LOOKUP_DC_IS_DNS_NAME, + DS_IS_DNS_NAME }, + { WBC_LOOKUP_DC_TRY_NEXTCLOSEST_SITE, + DS_TRY_NEXTCLOSEST_SITE }, + { WBC_LOOKUP_DC_DS_6_REQUIRED, + DS_DIRECTORY_SERVICE_6_REQUIRED }, + { WBC_LOOKUP_DC_RETURN_DNS_NAME, + DS_RETURN_DNS_NAME }, + { WBC_LOOKUP_DC_RETURN_FLAT_NAME, + DS_RETURN_FLAT_NAME } + }; + + uint32_t ds_flags = 0; + size_t i = 0; + + for (i=0; i<ARRAY_SIZE(lookup_dc_flags); i++) { + if (wbc_flags & lookup_dc_flags[i].wbc_dc_flag) { + ds_flags |= lookup_dc_flags[i].ds_dc_flags; + } + } + + return ds_flags; +} diff --git a/source3/winbindd/winbindd_dual.c b/source3/winbindd/winbindd_dual.c new file mode 100644 index 0000000..e63b405 --- /dev/null +++ b/source3/winbindd/winbindd_dual.c @@ -0,0 +1,2093 @@ +/* + Unix SMB/CIFS implementation. + + Winbind child daemons + + Copyright (C) Andrew Tridgell 2002 + Copyright (C) Volker Lendecke 2004,2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * We fork a child per domain to be able to act non-blocking in the main + * winbind daemon. A domain controller thousands of miles away being being + * slow replying with a 10.000 user list should not hold up netlogon calls + * that can be handled locally. + */ + +#include "includes.h" +#include "winbindd.h" +#include "rpc_client/rpc_client.h" +#include "nsswitch/wb_reqtrans.h" +#include "secrets.h" +#include "../lib/util/select.h" +#include "winbindd_traceid.h" +#include "../libcli/security/security.h" +#include "system/select.h" +#include "messages.h" +#include "../lib/util/tevent_unix.h" +#include "lib/param/loadparm.h" +#include "lib/util/sys_rw.h" +#include "lib/util/sys_rw_data.h" +#include "passdb.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" +#include "idmap.h" +#include "libcli/auth/netlogon_creds_cli.h" +#include "../lib/util/pidfile.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "lib/util/util_process.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +static void forall_domain_children(bool (*fn)(struct winbindd_child *c, + void *private_data), + void *private_data) +{ + struct winbindd_domain *d; + + for (d = domain_list(); d != NULL; d = d->next) { + int i; + + for (i = 0; i < talloc_array_length(d->children); i++) { + struct winbindd_child *c = &d->children[i]; + bool ok; + + if (c->pid == 0) { + continue; + } + + ok = fn(c, private_data); + if (!ok) { + return; + } + } + } +} + +static void forall_children(bool (*fn)(struct winbindd_child *c, + void *private_data), + void *private_data) +{ + struct winbindd_child *c; + bool ok; + + c = idmap_child(); + if (c->pid != 0) { + ok = fn(c, private_data); + if (!ok) { + return; + } + } + + c = locator_child(); + if (c->pid != 0) { + ok = fn(c, private_data); + if (!ok) { + return; + } + } + + forall_domain_children(fn, private_data); +} + +/* Read some data from a client connection */ + +static NTSTATUS child_read_request(int sock, struct winbindd_request *wreq) +{ + NTSTATUS status; + + status = read_data_ntstatus(sock, (char *)wreq, sizeof(*wreq)); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("child_read_request: read_data failed: %s\n", + nt_errstr(status))); + return status; + } + + if (wreq->extra_len == 0) { + wreq->extra_data.data = NULL; + return NT_STATUS_OK; + } + + DEBUG(10, ("Need to read %d extra bytes\n", (int)wreq->extra_len)); + + wreq->extra_data.data = SMB_MALLOC_ARRAY(char, wreq->extra_len + 1); + if (wreq->extra_data.data == NULL) { + DEBUG(0, ("malloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + + /* Ensure null termination */ + wreq->extra_data.data[wreq->extra_len] = '\0'; + + status = read_data_ntstatus(sock, wreq->extra_data.data, + wreq->extra_len); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Could not read extra data: %s\n", + nt_errstr(status))); + } + return status; +} + +static NTSTATUS child_write_response(int sock, struct winbindd_response *wrsp) +{ + struct iovec iov[2]; + int iov_count; + + iov[0].iov_base = (void *)wrsp; + iov[0].iov_len = sizeof(struct winbindd_response); + iov_count = 1; + + if (wrsp->length > sizeof(struct winbindd_response)) { + iov[1].iov_base = (void *)wrsp->extra_data.data; + iov[1].iov_len = wrsp->length-iov[0].iov_len; + iov_count = 2; + } + + DEBUG(10, ("Writing %d bytes to parent\n", (int)wrsp->length)); + + if (write_data_iov(sock, iov, iov_count) != wrsp->length) { + DEBUG(0, ("Could not write result\n")); + return NT_STATUS_INVALID_HANDLE; + } + + return NT_STATUS_OK; +} + +/* + * Do winbind child async request. This is not simply wb_simple_trans. We have + * to do the queueing ourselves because while a request is queued, the child + * might have crashed, and we have to re-fork it in the _trigger function. + */ + +struct wb_child_request_state { + struct tevent_context *ev; + struct tevent_req *queue_subreq; + struct tevent_req *subreq; + struct winbindd_child *child; + struct winbindd_request *request; + struct winbindd_response *response; +}; + +static bool fork_domain_child(struct winbindd_child *child); + +static void wb_child_request_waited(struct tevent_req *subreq); +static void wb_child_request_done(struct tevent_req *subreq); +static void wb_child_request_orphaned(struct tevent_req *subreq); + +static void wb_child_request_cleanup(struct tevent_req *req, + enum tevent_req_state req_state); + +struct tevent_req *wb_child_request_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_child *child, + struct winbindd_request *request) +{ + struct tevent_req *req; + struct wb_child_request_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct wb_child_request_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->child = child; + + /* + * We have to make a copy of "request", because our caller + * might drop us via talloc_free(). + * + * The talloc_move() magic in wb_child_request_cleanup() keeps + * all the requests, but if we are sitting deep within + * writev_send() down to the client, we have given it the + * pointer to "request". As our caller lost interest, it will + * just free "request", while writev_send still references it. + */ + + state->request = talloc_memdup(state, request, sizeof(*request)); + if (tevent_req_nomem(state->request, req)) { + return tevent_req_post(req, ev); + } + + state->request->traceid = debug_traceid_get(); + + if (request->extra_data.data != NULL) { + state->request->extra_data.data = talloc_memdup( + state->request, + request->extra_data.data, + request->extra_len); + if (tevent_req_nomem(state->request->extra_data.data, req)) { + return tevent_req_post(req, ev); + } + } + + subreq = tevent_queue_wait_send(state, ev, child->queue); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wb_child_request_waited, req); + state->queue_subreq = subreq; + + tevent_req_set_cleanup_fn(req, wb_child_request_cleanup); + + return req; +} + +static void wb_child_request_waited(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_child_request_state *state = tevent_req_data( + req, struct wb_child_request_state); + bool ok; + + ok = tevent_queue_wait_recv(subreq); + if (!ok) { + tevent_req_oom(req); + return; + } + /* + * We need to keep state->queue_subreq + * in order to block the queue. + */ + subreq = NULL; + + if ((state->child->sock == -1) && (!fork_domain_child(state->child))) { + tevent_req_error(req, errno); + return; + } + + tevent_fd_set_flags(state->child->monitor_fde, 0); + + subreq = wb_simple_trans_send(state, global_event_context(), NULL, + state->child->sock, state->request); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, wb_child_request_done, req); + tevent_req_set_endtime(req, state->ev, timeval_current_ofs(300, 0)); +} + +static void wb_child_request_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_child_request_state *state = tevent_req_data( + req, struct wb_child_request_state); + int ret, err; + + ret = wb_simple_trans_recv(subreq, state, &state->response, &err); + /* Freeing the subrequest is deferred until the cleanup function, + * which has to know whether a subrequest exists, and consequently + * decide whether to shut down the pipe to the child process. + */ + if (ret == -1) { + tevent_req_error(req, err); + return; + } + tevent_req_done(req); +} + +static void wb_child_request_orphaned(struct tevent_req *subreq) +{ + struct winbindd_child *child = + (struct winbindd_child *)tevent_req_callback_data_void(subreq); + + DBG_WARNING("cleanup orphaned subreq[%p]\n", subreq); + TALLOC_FREE(subreq); + + if (child->domain != NULL) { + /* + * If the child is attached to a domain, + * we need to make sure the domain queue + * can move forward, after the orphaned + * request is done. + */ + tevent_queue_start(child->domain->queue); + } +} + +int wb_child_request_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct winbindd_response **presponse, int *err) +{ + struct wb_child_request_state *state = tevent_req_data( + req, struct wb_child_request_state); + + if (tevent_req_is_unix_error(req, err)) { + return -1; + } + *presponse = talloc_move(mem_ctx, &state->response); + return 0; +} + +static void wb_child_request_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct wb_child_request_state *state = + tevent_req_data(req, struct wb_child_request_state); + + if (state->subreq == NULL) { + /* nothing to cleanup */ + return; + } + + if (req_state == TEVENT_REQ_RECEIVED) { + struct tevent_req *subreq = NULL; + + /* + * Our caller gave up, but we need to keep + * the low level request (wb_simple_trans) + * in order to maintain the parent child protocol. + * + * We also need to keep the child queue blocked + * until we got the response from the child. + */ + + subreq = talloc_move(state->child->queue, &state->subreq); + talloc_move(subreq, &state->queue_subreq); + talloc_move(subreq, &state->request); + tevent_req_set_callback(subreq, + wb_child_request_orphaned, + state->child); + + DBG_WARNING("keep orphaned subreq[%p]\n", subreq); + return; + } + + TALLOC_FREE(state->subreq); + TALLOC_FREE(state->queue_subreq); + + tevent_fd_set_flags(state->child->monitor_fde, TEVENT_FD_READ); + + if (state->child->domain != NULL) { + /* + * If the child is attached to a domain, + * we need to make sure the domain queue + * can move forward, after the request + * is done. + */ + tevent_queue_start(state->child->domain->queue); + } + + if (req_state == TEVENT_REQ_DONE) { + /* transmitted request and got response */ + return; + } + + /* + * Failed to transmit and receive response, or request + * cancelled while being serviced. + * The basic parent/child communication broke, close + * our socket + */ + TALLOC_FREE(state->child->monitor_fde); + close(state->child->sock); + state->child->sock = -1; +} + +static void child_socket_readable(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + struct winbindd_child *child = private_data; + + if ((flags & TEVENT_FD_READ) == 0) { + return; + } + + TALLOC_FREE(child->monitor_fde); + + /* + * We're only active when there is no outstanding child + * request. Arriving here means the child closed its socket, + * it died. Do the same here. + */ + + SMB_ASSERT(child->sock != -1); + + close(child->sock); + child->sock = -1; +} + +static struct winbindd_child *choose_domain_child(struct winbindd_domain *domain) +{ + struct winbindd_child *shortest = &domain->children[0]; + struct winbindd_child *current; + int i; + + for (i=0; i<talloc_array_length(domain->children); i++) { + size_t shortest_len, current_len; + + current = &domain->children[i]; + current_len = tevent_queue_length(current->queue); + + if (current_len == 0) { + /* idle child */ + return current; + } + + shortest_len = tevent_queue_length(shortest->queue); + + if (current_len < shortest_len) { + shortest = current; + } + } + + return shortest; +} + +struct dcerpc_binding_handle *dom_child_handle(struct winbindd_domain *domain) +{ + return domain->binding_handle; +} + +struct wb_domain_request_state { + struct tevent_context *ev; + struct tevent_queue_entry *queue_entry; + struct winbindd_domain *domain; + struct winbindd_child *child; + struct winbindd_request *request; + struct winbindd_request *init_req; + struct winbindd_response *response; + struct tevent_req *pending_subreq; + struct wbint_InitConnection r; +}; + +static void wb_domain_request_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct wb_domain_request_state *state = tevent_req_data( + req, struct wb_domain_request_state); + + /* + * If we're completely done or got a failure. + * we should remove ourself from the domain queue, + * after removing the child subreq from the child queue + * and give the next one in the queue the chance + * to check for an idle child. + */ + TALLOC_FREE(state->pending_subreq); + TALLOC_FREE(state->queue_entry); + tevent_queue_start(state->domain->queue); +} + +static void wb_domain_request_trigger(struct tevent_req *req, + void *private_data); +static void wb_domain_request_gotdc(struct tevent_req *subreq); +static void wb_domain_request_initialized(struct tevent_req *subreq); +static void wb_domain_request_done(struct tevent_req *subreq); + +struct tevent_req *wb_domain_request_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain, + struct winbindd_request *request) +{ + struct tevent_req *req; + struct wb_domain_request_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct wb_domain_request_state); + if (req == NULL) { + return NULL; + } + + state->domain = domain; + state->ev = ev; + state->request = request; + + tevent_req_set_cleanup_fn(req, wb_domain_request_cleanup); + + state->queue_entry = tevent_queue_add_entry( + domain->queue, state->ev, req, + wb_domain_request_trigger, NULL); + if (tevent_req_nomem(state->queue_entry, req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void wb_domain_request_trigger(struct tevent_req *req, + void *private_data) +{ + struct wb_domain_request_state *state = tevent_req_data( + req, struct wb_domain_request_state); + struct winbindd_domain *domain = state->domain; + struct tevent_req *subreq = NULL; + size_t shortest_queue_length; + + state->child = choose_domain_child(domain); + shortest_queue_length = tevent_queue_length(state->child->queue); + if (shortest_queue_length > 0) { + /* + * All children are busy, we need to stop + * the queue and untrigger our own queue + * entry. Once a pending request + * is done it calls tevent_queue_start + * and we get retriggered. + */ + state->child = NULL; + tevent_queue_stop(state->domain->queue); + tevent_queue_entry_untrigger(state->queue_entry); + return; + } + + if (domain->initialized) { + subreq = wb_child_request_send(state, state->ev, state->child, + state->request); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_domain_request_done, req); + state->pending_subreq = subreq; + + /* + * Once the domain is initialized and + * once we placed our real request into the child queue, + * we can remove ourself from the domain queue + * and give the next one in the queue the chance + * to check for an idle child. + */ + TALLOC_FREE(state->queue_entry); + return; + } + + state->init_req = talloc_zero(state, struct winbindd_request); + if (tevent_req_nomem(state->init_req, req)) { + return; + } + + if (IS_DC || domain->primary || domain->internal) { + /* The primary domain has to find the DC name itself */ + state->r.in.dcname = talloc_strdup(state, ""); + if (tevent_req_nomem(state->r.in.dcname, req)) { + return; + } + + subreq = dcerpc_wbint_InitConnection_r_send(state, + state->ev, + state->child->binding_handle, + &state->r); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_domain_request_initialized, + req); + state->pending_subreq = subreq; + return; + } + + /* + * This is *not* the primary domain, + * let's ask our DC about a DC name. + * + * We prefer getting a dns name in dc_unc, + * which is indicated by DS_RETURN_DNS_NAME. + * For NT4 domains we still get the netbios name. + */ + subreq = wb_dsgetdcname_send(state, state->ev, + state->domain->name, + NULL, /* domain_guid */ + NULL, /* site_name */ + DS_RETURN_DNS_NAME); /* flags */ + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_domain_request_gotdc, req); + state->pending_subreq = subreq; + return; +} + +static void wb_domain_request_gotdc(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_domain_request_state *state = tevent_req_data( + req, struct wb_domain_request_state); + struct netr_DsRGetDCNameInfo *dcinfo = NULL; + NTSTATUS status; + const char *dcname = NULL; + + state->pending_subreq = NULL; + + status = wb_dsgetdcname_recv(subreq, state, &dcinfo); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + dcname = dcinfo->dc_unc; + while (dcname != NULL && *dcname == '\\') { + dcname++; + } + + state->r.in.dcname = talloc_strdup(state, dcname); + if (tevent_req_nomem(state->r.in.dcname, req)) { + return; + } + + TALLOC_FREE(dcinfo); + + subreq = dcerpc_wbint_InitConnection_r_send(state, + state->ev, + state->child->binding_handle, + &state->r); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_domain_request_initialized, req); + state->pending_subreq = subreq; +} + +static void wb_domain_request_initialized(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_domain_request_state *state = tevent_req_data( + req, struct wb_domain_request_state); + NTSTATUS status; + + state->pending_subreq = NULL; + + status = dcerpc_wbint_InitConnection_r_recv(subreq, state); + TALLOC_FREE(subreq); + if (NT_STATUS_IS_ERR(status)) { + tevent_req_error(req, map_errno_from_nt_status(status)); + return; + } + + status = state->r.out.result; + if (NT_STATUS_IS_ERR(status)) { + tevent_req_error(req, map_errno_from_nt_status(status)); + return; + } + + state->domain->sid = *state->r.out.sid; + + talloc_free(state->domain->name); + state->domain->name = talloc_strdup(state->domain, *state->r.out.name); + if (state->domain->name == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + + if (*state->r.out.alt_name != NULL && + strlen(*state->r.out.alt_name) > 0) { + talloc_free(state->domain->alt_name); + + state->domain->alt_name = talloc_strdup(state->domain, + *state->r.out.alt_name); + if (state->domain->alt_name == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + } + + state->domain->native_mode = + (*state->r.out.flags & WB_DOMINFO_DOMAIN_NATIVE); + state->domain->active_directory = + (*state->r.out.flags & WB_DOMINFO_DOMAIN_AD); + state->domain->initialized = true; + + subreq = wb_child_request_send(state, state->ev, state->child, + state->request); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, wb_domain_request_done, req); + state->pending_subreq = subreq; + + /* + * Once the domain is initialized and + * once we placed our real request into the child queue, + * we can remove ourself from the domain queue + * and give the next one in the queue the chance + * to check for an idle child. + */ + TALLOC_FREE(state->queue_entry); +} + +static void wb_domain_request_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct wb_domain_request_state *state = tevent_req_data( + req, struct wb_domain_request_state); + int ret, err; + + state->pending_subreq = NULL; + + ret = wb_child_request_recv(subreq, talloc_tos(), &state->response, + &err); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_error(req, err); + return; + } + tevent_req_done(req); +} + +int wb_domain_request_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct winbindd_response **presponse, int *err) +{ + struct wb_domain_request_state *state = tevent_req_data( + req, struct wb_domain_request_state); + + if (tevent_req_is_unix_error(req, err)) { + return -1; + } + *presponse = talloc_move(mem_ctx, &state->response); + return 0; +} + +static void child_process_request(struct winbindd_child *child, + struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain = child->domain; + + /* Free response data - we may be interrupted and receive another + command before being able to send this data off. */ + + state->response->result = WINBINDD_ERROR; + state->response->length = sizeof(struct winbindd_response); + + /* as all requests in the child are sync, we can use talloc_tos() */ + state->mem_ctx = talloc_tos(); + + /* Process command */ + state->response->result = winbindd_dual_ndrcmd(domain, state); +} + +void setup_child(struct winbindd_domain *domain, struct winbindd_child *child, + const char *logprefix, + const char *logname) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + if (logprefix && logname) { + char *logbase = NULL; + + if (*lp_logfile(talloc_tos(), lp_sub)) { + char *end = NULL; + + if (asprintf(&logbase, "%s", lp_logfile(talloc_tos(), lp_sub)) < 0) { + smb_panic("Internal error: asprintf failed"); + } + + if ((end = strrchr_m(logbase, '/'))) { + *end = '\0'; + } + } else { + if (asprintf(&logbase, "%s", get_dyn_LOGFILEBASE()) < 0) { + smb_panic("Internal error: asprintf failed"); + } + } + + if (asprintf(&child->logfilename, "%s/%s-%s", + logbase, logprefix, logname) < 0) { + SAFE_FREE(logbase); + smb_panic("Internal error: asprintf failed"); + } + + SAFE_FREE(logbase); + } else { + smb_panic("Internal error: logprefix == NULL && " + "logname == NULL"); + } + + child->pid = 0; + child->sock = -1; + child->domain = domain; + child->queue = tevent_queue_create(NULL, "winbind_child"); + SMB_ASSERT(child->queue != NULL); + + child->binding_handle = wbint_binding_handle(NULL, NULL, child); + SMB_ASSERT(child->binding_handle != NULL); +} + +struct winbind_child_died_state { + pid_t pid; + struct winbindd_child *child; +}; + +static bool winbind_child_died_fn(struct winbindd_child *child, + void *private_data) +{ + struct winbind_child_died_state *state = private_data; + + if (child->pid == state->pid) { + state->child = child; + return false; + } + return true; +} + +void winbind_child_died(pid_t pid) +{ + struct winbind_child_died_state state = { .pid = pid }; + + forall_children(winbind_child_died_fn, &state); + + if (state.child == NULL) { + DEBUG(5, ("Already reaped child %u died\n", (unsigned int)pid)); + return; + } + + state.child->pid = 0; +} + +/* Ensure any negative cache entries with the netbios or realm names are removed. */ + +void winbindd_flush_negative_conn_cache(struct winbindd_domain *domain) +{ + flush_negative_conn_cache_for_domain(domain->name); + if (domain->alt_name != NULL) { + flush_negative_conn_cache_for_domain(domain->alt_name); + } +} + +/* + * Parent winbindd process sets its own debug level first and then + * sends a message to all the winbindd children to adjust their debug + * level to that of parents. + */ + +struct winbind_msg_relay_state { + struct messaging_context *msg_ctx; + uint32_t msg_type; + DATA_BLOB *data; +}; + +static bool winbind_msg_relay_fn(struct winbindd_child *child, + void *private_data) +{ + struct winbind_msg_relay_state *state = private_data; + + DBG_DEBUG("sending message to pid %u.\n", + (unsigned int)child->pid); + + messaging_send(state->msg_ctx, pid_to_procid(child->pid), + state->msg_type, state->data); + return true; +} + +void winbind_msg_debug(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbind_msg_relay_state state = { + .msg_ctx = msg_ctx, .msg_type = msg_type, .data = data + }; + + DEBUG(10,("winbind_msg_debug: got debug message.\n")); + + debug_message(msg_ctx, private_data, MSG_DEBUG, server_id, data); + + forall_children(winbind_msg_relay_fn, &state); +} + +void winbind_disconnect_dc_parent(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbind_msg_relay_state state = { + .msg_ctx = msg_ctx, .msg_type = msg_type, .data = data + }; + + DBG_DEBUG("Got disconnect_dc message\n"); + + forall_children(winbind_msg_relay_fn, &state); +} + +static bool winbindd_child_msg_filter(struct messaging_rec *rec, + void *private_data) +{ + struct winbindd_child *child = talloc_get_type_abort(private_data, + struct winbindd_child); + + if (rec->msg_type == MSG_SMB_CONF_UPDATED) { + DBG_DEBUG("Got reload-config message\n"); + winbindd_reload_services_file(child->logfilename); + } + + return false; +} + +/* React on 'smbcontrol winbindd reload-config' in the same way as on SIGHUP*/ +void winbindd_msg_reload_services_parent(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbind_msg_relay_state state = { + .msg_ctx = msg, + .msg_type = msg_type, + .data = data, + }; + bool ok; + + DBG_DEBUG("Got reload-config message\n"); + + /* Flush various caches */ + winbindd_flush_caches(); + + winbindd_reload_services_file((const char *)private_data); + + /* Set tevent_thread_call_depth_set_callback according to debug level */ + if (lp_winbind_debug_traceid() && debuglevel_get() > 1) { + tevent_thread_call_depth_set_callback(winbind_call_flow, NULL); + } else { + tevent_thread_call_depth_set_callback(NULL, NULL); + } + + ok = add_trusted_domains_dc(); + if (!ok) { + DBG_ERR("add_trusted_domains_dc() failed\n"); + } + + forall_children(winbind_msg_relay_fn, &state); +} + +/* Set our domains as offline and forward the offline message to our children. */ + +struct winbind_msg_on_offline_state { + struct messaging_context *msg_ctx; + uint32_t msg_type; +}; + +static bool winbind_msg_on_offline_fn(struct winbindd_child *child, + void *private_data) +{ + struct winbind_msg_on_offline_state *state = private_data; + + if (child->domain->internal) { + return true; + } + + /* + * Each winbindd child should only process requests for one + * domain - make sure we only set it online / offline for that + * domain. + */ + DBG_DEBUG("sending message to pid %u for domain %s.\n", + (unsigned int)child->pid, child->domain->name); + + messaging_send_buf(state->msg_ctx, + pid_to_procid(child->pid), + state->msg_type, + (const uint8_t *)child->domain->name, + strlen(child->domain->name)+1); + + return true; +} + +void winbind_msg_offline(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbind_msg_on_offline_state state = { + .msg_ctx = msg_ctx, + .msg_type = MSG_WINBIND_OFFLINE, + }; + struct winbindd_domain *domain; + + DEBUG(10,("winbind_msg_offline: got offline message.\n")); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("winbind_msg_offline: rejecting offline message.\n")); + return; + } + + /* Set our global state as offline. */ + if (!set_global_winbindd_state_offline()) { + DEBUG(10,("winbind_msg_offline: offline request failed.\n")); + return; + } + + /* Set all our domains as offline. */ + for (domain = domain_list(); domain; domain = domain->next) { + if (domain->internal) { + continue; + } + DEBUG(5,("winbind_msg_offline: marking %s offline.\n", domain->name)); + domain->online = false; + } + + forall_domain_children(winbind_msg_on_offline_fn, &state); +} + +/* Set our domains as online and forward the online message to our children. */ + +void winbind_msg_online(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbind_msg_on_offline_state state = { + .msg_ctx = msg_ctx, + .msg_type = MSG_WINBIND_ONLINE, + }; + + DEBUG(10,("winbind_msg_online: got online message.\n")); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("winbind_msg_online: rejecting online message.\n")); + return; + } + + /* Set our global state as online. */ + set_global_winbindd_state_online(); + + smb_nscd_flush_user_cache(); + smb_nscd_flush_group_cache(); + + /* Tell all our child domains to go online online. */ + forall_domain_children(winbind_msg_on_offline_fn, &state); +} + +static const char *collect_onlinestatus(TALLOC_CTX *mem_ctx) +{ + struct winbindd_domain *domain; + char *buf = NULL; + + if ((buf = talloc_asprintf(mem_ctx, "global:%s ", + get_global_winbindd_state_offline() ? + "Offline":"Online")) == NULL) { + return NULL; + } + + for (domain = domain_list(); domain; domain = domain->next) { + if ((buf = talloc_asprintf_append_buffer(buf, "%s:%s ", + domain->name, + domain->online ? + "Online":"Offline")) == NULL) { + return NULL; + } + } + + buf = talloc_asprintf_append_buffer(buf, "\n"); + + DEBUG(5,("collect_onlinestatus: %s", buf)); + + return buf; +} + +void winbind_msg_onlinestatus(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + TALLOC_CTX *mem_ctx; + const char *message; + + DEBUG(5,("winbind_msg_onlinestatus received.\n")); + + mem_ctx = talloc_init("winbind_msg_onlinestatus"); + if (mem_ctx == NULL) { + return; + } + + message = collect_onlinestatus(mem_ctx); + if (message == NULL) { + talloc_destroy(mem_ctx); + return; + } + + messaging_send_buf(msg_ctx, server_id, MSG_WINBIND_ONLINESTATUS, + (const uint8_t *)message, strlen(message) + 1); + + talloc_destroy(mem_ctx); +} + +void winbind_msg_dump_domain_list(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + TALLOC_CTX *mem_ctx; + const char *message = NULL; + const char *domain = NULL; + char *s = NULL; + NTSTATUS status; + struct winbindd_domain *dom = NULL; + + DEBUG(5,("winbind_msg_dump_domain_list received.\n")); + + mem_ctx = talloc_init("winbind_msg_dump_domain_list"); + if (!mem_ctx) { + return; + } + + if (data->length > 0) { + domain = (const char *)data->data; + } + + if (domain) { + + DEBUG(5,("winbind_msg_dump_domain_list for domain: %s\n", + domain)); + + message = NDR_PRINT_STRUCT_STRING(mem_ctx, winbindd_domain, + find_domain_from_name_noinit(domain)); + if (!message) { + talloc_destroy(mem_ctx); + return; + } + + messaging_send_buf(msg_ctx, server_id, + MSG_WINBIND_DUMP_DOMAIN_LIST, + (const uint8_t *)message, strlen(message) + 1); + + talloc_destroy(mem_ctx); + + return; + } + + DEBUG(5,("winbind_msg_dump_domain_list all domains\n")); + + for (dom = domain_list(); dom; dom=dom->next) { + message = NDR_PRINT_STRUCT_STRING(mem_ctx, winbindd_domain, dom); + if (!message) { + talloc_destroy(mem_ctx); + return; + } + + s = talloc_asprintf_append(s, "%s\n", message); + if (!s) { + talloc_destroy(mem_ctx); + return; + } + } + + status = messaging_send_buf(msg_ctx, server_id, + MSG_WINBIND_DUMP_DOMAIN_LIST, + (uint8_t *)s, strlen(s) + 1); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("failed to send message: %s\n", + nt_errstr(status))); + } + + talloc_destroy(mem_ctx); +} + +static void account_lockout_policy_handler(struct tevent_context *ctx, + struct tevent_timer *te, + struct timeval now, + void *private_data) +{ + struct winbindd_child *child = + (struct winbindd_child *)private_data; + TALLOC_CTX *mem_ctx = NULL; + struct samr_DomInfo12 lockout_policy; + NTSTATUS result; + + DEBUG(10,("account_lockout_policy_handler called\n")); + + TALLOC_FREE(child->lockout_policy_event); + + if ( !winbindd_can_contact_domain( child->domain ) ) { + DEBUG(10,("account_lockout_policy_handler: Removing myself since I " + "do not have an incoming trust to domain %s\n", + child->domain->name)); + + return; + } + + mem_ctx = talloc_init("account_lockout_policy_handler ctx"); + if (!mem_ctx) { + result = NT_STATUS_NO_MEMORY; + } else { + result = wb_cache_lockout_policy(child->domain, mem_ctx, + &lockout_policy); + } + TALLOC_FREE(mem_ctx); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("account_lockout_policy_handler: lockout_policy failed error %s\n", + nt_errstr(result))); + } + + child->lockout_policy_event = tevent_add_timer(global_event_context(), NULL, + timeval_current_ofs(3600, 0), + account_lockout_policy_handler, + child); +} + +static time_t get_machine_password_timeout(void) +{ + /* until we have gpo support use lp setting */ + return lp_machine_password_timeout(); +} + +static bool calculate_next_machine_pwd_change(const char *domain, + struct timeval *t) +{ + time_t pass_last_set_time; + time_t timeout; + time_t next_change; + struct timeval tv; + char *pw; + + pw = secrets_fetch_machine_password(domain, + &pass_last_set_time, + NULL); + + if (pw == NULL) { + DEBUG(0,("cannot fetch own machine password ????\n")); + return false; + } + + SAFE_FREE(pw); + + timeout = get_machine_password_timeout(); + if (timeout == 0) { + DEBUG(10,("machine password never expires\n")); + return false; + } + + tv.tv_sec = pass_last_set_time; + DEBUG(10, ("password last changed %s\n", + timeval_string(talloc_tos(), &tv, false))); + tv.tv_sec += timeout; + DEBUGADD(10, ("password valid until %s\n", + timeval_string(talloc_tos(), &tv, false))); + + 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(talloc_tos(), next_change))); + *t = timeval_set(next_change, 0); + + if (lp_clustering()) { + uint8_t randbuf; + /* + * When having a cluster, we have several + * winbinds racing for the password change. In + * the machine_password_change_handler() + * function we check if someone else was + * faster when the event triggers. We add a + * 255-second random delay here, so that we + * don't run to change the password at the + * exact same moment. + */ + generate_random_buffer(&randbuf, sizeof(randbuf)); + DEBUG(10, ("adding %d seconds randomness\n", + (int)randbuf)); + t->tv_sec += randbuf; + } + return true; + } + + DEBUG(10,("machine password expired, needs immediate change\n")); + + *t = timeval_zero(); + + return true; +} + +static void machine_password_change_handler(struct tevent_context *ctx, + struct tevent_timer *te, + struct timeval now, + void *private_data) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + struct winbindd_child *child = + (struct winbindd_child *)private_data; + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + NTSTATUS result; + struct timeval next_change; + + DEBUG(10,("machine_password_change_handler called\n")); + + TALLOC_FREE(child->machine_password_change_event); + + if (!calculate_next_machine_pwd_change(child->domain->name, + &next_change)) { + DEBUG(10, ("calculate_next_machine_pwd_change failed\n")); + return; + } + + DEBUG(10, ("calculate_next_machine_pwd_change returned %s\n", + timeval_string(talloc_tos(), &next_change, false))); + + if (!timeval_expired(&next_change)) { + DEBUG(10, ("Someone else has already changed the pw\n")); + goto done; + } + + if (!winbindd_can_contact_domain(child->domain)) { + DEBUG(10,("machine_password_change_handler: Removing myself since I " + "do not have an incoming trust to domain %s\n", + child->domain->name)); + return; + } + + result = cm_connect_netlogon_secure(child->domain, + &netlogon_pipe, + &netlogon_creds_ctx); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("machine_password_change_handler: " + "failed to connect netlogon pipe: %s\n", + nt_errstr(result))); + return; + } + + result = trust_pw_change(netlogon_creds_ctx, + msg_ctx, + netlogon_pipe->binding_handle, + child->domain->name, + child->domain->dcname, + false); /* force */ + + DEBUG(10, ("machine_password_change_handler: " + "trust_pw_change returned %s\n", + nt_errstr(result))); + + if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED) ) { + DEBUG(3,("machine_password_change_handler: password set returned " + "ACCESS_DENIED. Maybe the trust account " + "password was changed and we didn't know it. " + "Killing connections to domain %s\n", + child->domain->name)); + invalidate_cm_connection(child->domain); + } + + if (!calculate_next_machine_pwd_change(child->domain->name, + &next_change)) { + DEBUG(10, ("calculate_next_machine_pwd_change failed\n")); + return; + } + + DEBUG(10, ("calculate_next_machine_pwd_change returned %s\n", + timeval_string(talloc_tos(), &next_change, false))); + + if (!NT_STATUS_IS_OK(result)) { + struct timeval tmp; + /* + * In case of failure, give the DC a minute to recover + */ + tmp = timeval_current_ofs(60, 0); + next_change = timeval_max(&next_change, &tmp); + } + +done: + child->machine_password_change_event = tevent_add_timer(global_event_context(), NULL, + next_change, + machine_password_change_handler, + child); +} + +/* Deal with a request to go offline. */ + +static void child_msg_offline(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbindd_domain *domain; + struct winbindd_domain *primary_domain = NULL; + const char *domainname = (const char *)data->data; + + if (data->data == NULL || data->length == 0) { + return; + } + + DEBUG(5,("child_msg_offline received for domain %s.\n", domainname)); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("child_msg_offline: rejecting offline message.\n")); + return; + } + + primary_domain = find_our_domain(); + + /* Mark the requested domain offline. */ + + for (domain = domain_list(); domain; domain = domain->next) { + if (domain->internal) { + continue; + } + if (strequal(domain->name, domainname)) { + DEBUG(5,("child_msg_offline: marking %s offline.\n", domain->name)); + set_domain_offline(domain); + /* we are in the trusted domain, set the primary domain + * offline too */ + if (domain != primary_domain) { + set_domain_offline(primary_domain); + } + } + } +} + +/* Deal with a request to go online. */ + +static void child_msg_online(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbindd_domain *domain; + struct winbindd_domain *primary_domain = NULL; + const char *domainname = (const char *)data->data; + + if (data->data == NULL || data->length == 0) { + return; + } + + DEBUG(5,("child_msg_online received for domain %s.\n", domainname)); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("child_msg_online: rejecting online message.\n")); + return; + } + + primary_domain = find_our_domain(); + + /* Set our global state as online. */ + set_global_winbindd_state_online(); + + /* Try and mark everything online - delete any negative cache entries + to force a reconnect now. */ + + for (domain = domain_list(); domain; domain = domain->next) { + if (domain->internal) { + continue; + } + if (strequal(domain->name, domainname)) { + DEBUG(5,("child_msg_online: requesting %s to go online.\n", domain->name)); + winbindd_flush_negative_conn_cache(domain); + set_domain_online_request(domain); + + /* we can be in trusted domain, which will contact primary domain + * we have to bring primary domain online in trusted domain process + * see, winbindd_dual_pam_auth() --> winbindd_dual_pam_auth_samlogon() + * --> contact_domain = find_our_domain() + * */ + if (domain != primary_domain) { + winbindd_flush_negative_conn_cache(primary_domain); + set_domain_online_request(primary_domain); + } + } + } +} + +struct winbindd_reinit_after_fork_state { + const struct winbindd_child *myself; +}; + +static bool winbindd_reinit_after_fork_fn(struct winbindd_child *child, + void *private_data) +{ + struct winbindd_reinit_after_fork_state *state = private_data; + + if (child == state->myself) { + return true; + } + + /* Destroy all possible events in child list. */ + TALLOC_FREE(child->lockout_policy_event); + TALLOC_FREE(child->machine_password_change_event); + + /* + * Children should never be able to send each other messages, + * all messages must go through the parent. + */ + child->pid = (pid_t)0; + + /* + * Close service sockets to all other children + */ + if (child->sock != -1) { + close(child->sock); + child->sock = -1; + } + + return true; +} + +NTSTATUS winbindd_reinit_after_fork(const struct winbindd_child *myself, + const char *logfilename) +{ + struct winbindd_reinit_after_fork_state state = { .myself = myself }; + struct winbindd_domain *domain; + NTSTATUS status; + + status = reinit_after_fork( + global_messaging_context(), + global_event_context(), + true); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("reinit_after_fork() failed\n")); + return status; + } + initialize_password_db(true, global_event_context()); + + close_conns_after_fork(); + + if (logfilename != NULL) { + lp_set_logfile(logfilename); + reopen_logs(); + } + + if (!winbindd_setup_sig_term_handler(false)) { + return NT_STATUS_NO_MEMORY; + } + + if (!winbindd_setup_sig_hup_handler(logfilename)) { + return NT_STATUS_NO_MEMORY; + } + + /* Stop zombies in children */ + CatchChild(); + + /* Don't handle the same messages as our parent. */ + messaging_deregister(global_messaging_context(), + MSG_SMB_CONF_UPDATED, NULL); + messaging_deregister(global_messaging_context(), + MSG_SHUTDOWN, NULL); + messaging_deregister(global_messaging_context(), + MSG_WINBIND_OFFLINE, NULL); + messaging_deregister(global_messaging_context(), + MSG_WINBIND_ONLINE, NULL); + messaging_deregister(global_messaging_context(), + MSG_WINBIND_ONLINESTATUS, NULL); + messaging_deregister(global_messaging_context(), + MSG_WINBIND_DUMP_DOMAIN_LIST, NULL); + messaging_deregister(global_messaging_context(), + MSG_DEBUG, NULL); + + messaging_deregister(global_messaging_context(), + MSG_WINBIND_DOMAIN_OFFLINE, NULL); + messaging_deregister(global_messaging_context(), + MSG_WINBIND_DOMAIN_ONLINE, NULL); + + /* We have destroyed all events in the winbindd_event_context + * in reinit_after_fork(), so clean out all possible pending + * event pointers. */ + + /* Deal with check_online_events. */ + + for (domain = domain_list(); domain; domain = domain->next) { + TALLOC_FREE(domain->check_online_event); + } + + /* Ensure we're not handling a credential cache event inherited + * from our parent. */ + + ccache_remove_all_after_fork(); + + forall_children(winbindd_reinit_after_fork_fn, &state); + + return NT_STATUS_OK; +} + +/* + * In a child there will be only one domain, reference that here. + */ +static struct winbindd_domain *child_domain; + +struct winbindd_domain *wb_child_domain(void) +{ + return child_domain; +} + +struct child_handler_state { + struct winbindd_child *child; + struct winbindd_cli_state cli; +}; + +static void child_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct child_handler_state *state = + (struct child_handler_state *)private_data; + NTSTATUS status; + uint64_t parent_traceid; + + /* fetch a request from the main daemon */ + status = child_read_request(state->cli.sock, state->cli.request); + + if (!NT_STATUS_IS_OK(status)) { + /* we lost contact with our parent */ + _exit(0); + } + + /* read traceid from request */ + parent_traceid = state->cli.request->traceid; + debug_traceid_set(parent_traceid); + + DEBUG(4,("child daemon request %d\n", + (int)state->cli.request->cmd)); + + ZERO_STRUCTP(state->cli.response); + state->cli.request->null_term = '\0'; + state->cli.mem_ctx = talloc_tos(); + child_process_request(state->child, &state->cli); + + DEBUG(4, ("Finished processing child request %d\n", + (int)state->cli.request->cmd)); + + SAFE_FREE(state->cli.request->extra_data.data); + + status = child_write_response(state->cli.sock, state->cli.response); + if (!NT_STATUS_IS_OK(status)) { + exit(1); + } +} + +static bool fork_domain_child(struct winbindd_child *child) +{ + int fdpair[2]; + struct child_handler_state state; + struct winbindd_request request; + struct winbindd_response response; + struct winbindd_domain *primary_domain = NULL; + NTSTATUS status; + ssize_t nwritten; + struct tevent_fd *fde; + struct tevent_req *req = NULL; + + if (child->domain) { + DEBUG(10, ("fork_domain_child called for domain '%s'\n", + child->domain->name)); + } else { + DEBUG(10, ("fork_domain_child called without domain.\n")); + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) != 0) { + DEBUG(0, ("Could not open child pipe: %s\n", + strerror(errno))); + return False; + } + + ZERO_STRUCT(state); + state.child = child; + state.cli.pid = getpid(); + state.cli.request = &request; + state.cli.response = &response; + + child->pid = fork(); + + if (child->pid == -1) { + DEBUG(0, ("Could not fork: %s\n", strerror(errno))); + close(fdpair[0]); + close(fdpair[1]); + return False; + } + + if (child->pid != 0) { + /* Parent */ + ssize_t nread; + int rc; + + close(fdpair[0]); + + nread = sys_read(fdpair[1], &status, sizeof(status)); + if (nread != sizeof(status)) { + DEBUG(1, ("fork_domain_child: Could not read child status: " + "nread=%d, error=%s\n", (int)nread, + strerror(errno))); + close(fdpair[1]); + return false; + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("fork_domain_child: Child status is %s\n", + nt_errstr(status))); + close(fdpair[1]); + return false; + } + + child->monitor_fde = tevent_add_fd(global_event_context(), + global_event_context(), + fdpair[1], + TEVENT_FD_READ, + child_socket_readable, + child); + if (child->monitor_fde == NULL) { + DBG_WARNING("tevent_add_fd failed\n"); + close(fdpair[1]); + return false; + } + + rc = set_blocking(fdpair[1], false); + if (rc < 0) { + close(fdpair[1]); + return false; + } + + child->sock = fdpair[1]; + + return true; + } + + /* Child */ + child_domain = child->domain; + + DEBUG(10, ("Child process %d\n", (int)getpid())); + + state.cli.sock = fdpair[0]; + close(fdpair[1]); + + /* Reset traceid and deactivate call_depth tracking */ + if (lp_winbind_debug_traceid()) { + debug_traceid_set(1); + tevent_thread_call_depth_set_callback(NULL, NULL); + } + + status = winbindd_reinit_after_fork(child, child->logfilename); + + /* setup callbacks again, one of them is removed in reinit_after_fork */ + if (lp_winbind_debug_traceid()) { + winbind_debug_traceid_setup(global_event_context()); + } + + nwritten = sys_write(state.cli.sock, &status, sizeof(status)); + if (nwritten != sizeof(status)) { + DEBUG(1, ("fork_domain_child: Could not write status: " + "nwritten=%d, error=%s\n", (int)nwritten, + strerror(errno))); + _exit(0); + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("winbindd_reinit_after_fork failed: %s\n", + nt_errstr(status))); + _exit(0); + } + + if (child_domain != NULL) { + process_set_title("wb[%s]", "domain child [%s]", child_domain->name); + } else if (is_idmap_child(child)) { + process_set_title("wb-idmap", "idmap child"); + } + + /* Handle online/offline messages. */ + messaging_register(global_messaging_context(), NULL, + MSG_WINBIND_OFFLINE, child_msg_offline); + messaging_register(global_messaging_context(), NULL, + MSG_WINBIND_ONLINE, child_msg_online); + messaging_register(global_messaging_context(), NULL, + MSG_DEBUG, debug_message); + messaging_register(global_messaging_context(), NULL, + MSG_WINBIND_IP_DROPPED, + winbind_msg_ip_dropped); + messaging_register(global_messaging_context(), NULL, + MSG_WINBIND_DISCONNECT_DC, + winbind_msg_disconnect_dc); + + req = messaging_filtered_read_send(global_event_context(), + global_event_context(), + global_messaging_context(), + winbindd_child_msg_filter, + child); + if (req == NULL) { + DBG_ERR("messaging_filtered_read_send failed\n"); + _exit(1); + } + + primary_domain = find_our_domain(); + + if (primary_domain == NULL) { + smb_panic("no primary domain found"); + } + + /* It doesn't matter if we allow cache login, + * try to bring domain online after fork. */ + if ( child->domain ) { + child->domain->startup = True; + child->domain->startup_time = time_mono(NULL); + /* we can be in primary domain or in trusted domain + * If we are in trusted domain, set the primary domain + * in start-up mode */ + if (!(child->domain->internal)) { + set_domain_online_request(child->domain); + if (!(child->domain->primary)) { + primary_domain->startup = True; + primary_domain->startup_time = time_mono(NULL); + set_domain_online_request(primary_domain); + } + } + } + + /* We might be in the idmap child...*/ + if (child->domain && !(child->domain->internal) && + lp_winbind_offline_logon()) { + + set_domain_online_request(child->domain); + + if (primary_domain && (primary_domain != child->domain)) { + /* We need to talk to the primary + * domain as well as the trusted + * domain inside a trusted domain + * child. + * See the code in : + * set_dc_type_and_flags_trustinfo() + * for details. + */ + set_domain_online_request(primary_domain); + } + + child->lockout_policy_event = tevent_add_timer( + global_event_context(), NULL, timeval_zero(), + account_lockout_policy_handler, + child); + } + + if (child->domain && child->domain->primary && + !USE_KERBEROS_KEYTAB && + lp_server_role() == ROLE_DOMAIN_MEMBER) { + + struct timeval next_change; + + if (calculate_next_machine_pwd_change(child->domain->name, + &next_change)) { + child->machine_password_change_event = tevent_add_timer( + global_event_context(), NULL, next_change, + machine_password_change_handler, + child); + } + } + + fde = tevent_add_fd(global_event_context(), NULL, state.cli.sock, + TEVENT_FD_READ, child_handler, &state); + if (fde == NULL) { + DEBUG(1, ("tevent_add_fd failed\n")); + _exit(1); + } + + while (1) { + + int ret; + TALLOC_CTX *frame = talloc_stackframe(); + + ret = tevent_loop_once(global_event_context()); + if (ret != 0) { + DEBUG(1, ("tevent_loop_once failed: %s\n", + strerror(errno))); + _exit(1); + } + + if (child->domain && child->domain->startup && + (time_mono(NULL) > child->domain->startup_time + 30)) { + /* No longer in "startup" mode. */ + DEBUG(10,("fork_domain_child: domain %s no longer in 'startup' mode.\n", + child->domain->name )); + child->domain->startup = False; + } + + TALLOC_FREE(frame); + } +} + +void winbind_msg_ip_dropped_parent(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + struct winbind_msg_relay_state state = { + .msg_ctx = msg_ctx, + .msg_type = msg_type, + .data = data, + }; + + winbind_msg_ip_dropped(msg_ctx, private_data, msg_type, + server_id, data); + + forall_children(winbind_msg_relay_fn, &state); +} + +void winbindd_terminate(bool is_parent) +{ + if (is_parent) { + /* When parent goes away we should + * remove the socket file. Not so + * when children terminate. + */ + char *path = NULL; + + if (asprintf(&path, "%s/%s", + lp_winbindd_socket_directory(), WINBINDD_SOCKET_NAME) > 0) { + unlink(path); + SAFE_FREE(path); + } + } + + idmap_close(); + + netlogon_creds_cli_close_global_db(); + +#if 0 + if (interactive) { + TALLOC_CTX *mem_ctx = talloc_init("end_description"); + char *description = talloc_describe_all(mem_ctx); + + DEBUG(3, ("tallocs left:\n%s\n", description)); + talloc_destroy(mem_ctx); + } +#endif + + if (is_parent) { + pidfile_unlink(lp_pid_directory(), "winbindd"); + } + + exit(0); +} + +static void winbindd_sig_term_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + bool *p = talloc_get_type_abort(private_data, bool); + bool is_parent = *p; + + TALLOC_FREE(p); + + DEBUG(0,("Got sig[%d] terminate (is_parent=%d)\n", + signum, is_parent)); + winbindd_terminate(is_parent); +} + +bool winbindd_setup_sig_term_handler(bool parent) +{ + struct tevent_signal *se; + bool *is_parent; + + is_parent = talloc(global_event_context(), bool); + if (!is_parent) { + return false; + } + + *is_parent = parent; + + se = tevent_add_signal(global_event_context(), + is_parent, + SIGTERM, 0, + winbindd_sig_term_handler, + is_parent); + if (!se) { + DEBUG(0,("failed to setup SIGTERM handler\n")); + talloc_free(is_parent); + return false; + } + + se = tevent_add_signal(global_event_context(), + is_parent, + SIGINT, 0, + winbindd_sig_term_handler, + is_parent); + if (!se) { + DEBUG(0,("failed to setup SIGINT handler\n")); + talloc_free(is_parent); + return false; + } + + se = tevent_add_signal(global_event_context(), + is_parent, + SIGQUIT, 0, + winbindd_sig_term_handler, + is_parent); + if (!se) { + DEBUG(0,("failed to setup SIGINT handler\n")); + talloc_free(is_parent); + return false; + } + + return true; +} + +static void flush_caches_noinit(void) +{ + /* + * We need to invalidate cached user list entries on a SIGHUP + * otherwise cached access denied errors due to restrict anonymous + * hang around until the sequence number changes. + * NB + * Skip uninitialized domains when flush cache. + * If domain is not initialized, it means it is never + * used or never become online. look, wcache_invalidate_cache() + * -> get_cache() -> init_dc_connection(). It causes a lot of traffic + * for unused domains and large traffic for primary domain's DC if there + * are many domains.. + */ + + if (!wcache_invalidate_cache_noinit()) { + DEBUG(0, ("invalidating the cache failed; revalidate the cache\n")); + if (!winbindd_cache_validate_and_initialize()) { + exit(1); + } + } +} + +static void winbindd_sig_hup_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + const char *file = (const char *)private_data; + + DEBUG(1,("Reloading services after SIGHUP\n")); + flush_caches_noinit(); + winbindd_reload_services_file(file); +} + +bool winbindd_setup_sig_hup_handler(const char *lfile) +{ + struct tevent_signal *se; + char *file = NULL; + + if (lfile) { + file = talloc_strdup(global_event_context(), + lfile); + if (!file) { + return false; + } + } + + se = tevent_add_signal(global_event_context(), + global_event_context(), + SIGHUP, 0, + winbindd_sig_hup_handler, + file); + if (!se) { + return false; + } + + return true; +} diff --git a/source3/winbindd/winbindd_dual_ndr.c b/source3/winbindd/winbindd_dual_ndr.c new file mode 100644 index 0000000..7835bd3 --- /dev/null +++ b/source3/winbindd/winbindd_dual_ndr.c @@ -0,0 +1,615 @@ +/* + Unix SMB/CIFS implementation. + + Provide parent->child communication based on NDR marshalling + + Copyright (C) Volker Lendecke 2009 + + 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/>. +*/ + +/* + * This file implements an RPC between winbind parent and child processes, + * leveraging the autogenerated marshalling routines for MSRPC. This is not + * MSRPC, as it does not go through the whole DCERPC fragmentation, we just + * leverage much the same infrastructure we already have for it. + */ + +#include "includes.h" +#include "winbindd/winbindd.h" +#include "winbindd/winbindd_proto.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_winbind.h" +#include "rpc_server/rpc_config.h" +#include "rpc_server/rpc_server.h" +#include "rpc_dce.h" +#include "lib/tsocket/tsocket.h" + +struct wbint_bh_state { + struct winbindd_domain *domain; + struct winbindd_child *child; +}; + +static bool wbint_bh_is_connected(struct dcerpc_binding_handle *h) +{ + struct wbint_bh_state *hs = dcerpc_binding_handle_data(h, + struct wbint_bh_state); + + if ((hs->domain == NULL) && (hs->child == NULL)) { + return false; + } + + return true; +} + +static uint32_t wbint_bh_set_timeout(struct dcerpc_binding_handle *h, + uint32_t timeout) +{ + /* TODO: implement timeouts */ + return UINT32_MAX; +} + +struct wbint_bh_raw_call_state { + struct winbindd_domain *domain; + uint32_t opnum; + DATA_BLOB in_data; + struct winbindd_request request; + struct winbindd_response *response; + DATA_BLOB out_data; +}; + +static void wbint_bh_raw_call_child_done(struct tevent_req *subreq); +static void wbint_bh_raw_call_domain_done(struct tevent_req *subreq); + +static struct tevent_req *wbint_bh_raw_call_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h, + const struct GUID *object, + uint32_t opnum, + uint32_t in_flags, + const uint8_t *in_data, + size_t in_length) +{ + struct wbint_bh_state *hs = + dcerpc_binding_handle_data(h, + struct wbint_bh_state); + struct tevent_req *req; + struct wbint_bh_raw_call_state *state; + bool ok; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct wbint_bh_raw_call_state); + if (req == NULL) { + return NULL; + } + state->domain = hs->domain; + state->opnum = opnum; + state->in_data.data = discard_const_p(uint8_t, in_data); + state->in_data.length = in_length; + + ok = wbint_bh_is_connected(h); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED); + return tevent_req_post(req, ev); + } + + if ((state->domain != NULL) + && wcache_fetch_ndr(state, state->domain, state->opnum, + &state->in_data, &state->out_data)) { + DBG_DEBUG("Got opnum %"PRIu32" for domain %s from cache\n", + state->opnum, state->domain->name); + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + state->request.cmd = WINBINDD_DUAL_NDRCMD; + state->request.data.ndrcmd = state->opnum; + state->request.extra_data.data = (char *)state->in_data.data; + state->request.extra_len = state->in_data.length; + + if (hs->child != NULL) { + subreq = wb_child_request_send(state, ev, hs->child, + &state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, wbint_bh_raw_call_child_done, req); + return req; + } + + subreq = wb_domain_request_send(state, ev, hs->domain, + &state->request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, wbint_bh_raw_call_domain_done, req); + + return req; +} + +static void wbint_bh_raw_call_child_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct wbint_bh_raw_call_state *state = + tevent_req_data(req, + struct wbint_bh_raw_call_state); + int ret, err; + + ret = wb_child_request_recv(subreq, state, &state->response, &err); + TALLOC_FREE(subreq); + if (ret == -1) { + NTSTATUS status = map_nt_error_from_unix(err); + tevent_req_nterror(req, status); + return; + } + + state->out_data = data_blob_talloc(state, + state->response->extra_data.data, + state->response->length - sizeof(struct winbindd_response)); + if (state->response->extra_data.data && !state->out_data.data) { + tevent_req_oom(req); + return; + } + + if (state->domain != NULL) { + wcache_store_ndr(state->domain, state->opnum, + &state->in_data, &state->out_data); + } + + tevent_req_done(req); +} + +static void wbint_bh_raw_call_domain_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct wbint_bh_raw_call_state *state = + tevent_req_data(req, + struct wbint_bh_raw_call_state); + int ret, err; + + ret = wb_domain_request_recv(subreq, state, &state->response, &err); + TALLOC_FREE(subreq); + if (ret == -1) { + NTSTATUS status = map_nt_error_from_unix(err); + tevent_req_nterror(req, status); + return; + } + + state->out_data = data_blob_talloc(state, + state->response->extra_data.data, + state->response->length - sizeof(struct winbindd_response)); + if (state->response->extra_data.data && !state->out_data.data) { + tevent_req_oom(req); + return; + } + + if (state->domain != NULL) { + wcache_store_ndr(state->domain, state->opnum, + &state->in_data, &state->out_data); + } + + tevent_req_done(req); +} + +static NTSTATUS wbint_bh_raw_call_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t **out_data, + size_t *out_length, + uint32_t *out_flags) +{ + struct wbint_bh_raw_call_state *state = + tevent_req_data(req, + struct wbint_bh_raw_call_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out_data = talloc_move(mem_ctx, &state->out_data.data); + *out_length = state->out_data.length; + *out_flags = 0; + tevent_req_received(req); + return NT_STATUS_OK; +} + +struct wbint_bh_disconnect_state { + uint8_t _dummy; +}; + +static struct tevent_req *wbint_bh_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dcerpc_binding_handle *h) +{ + struct wbint_bh_state *hs = dcerpc_binding_handle_data(h, + struct wbint_bh_state); + struct tevent_req *req; + struct wbint_bh_disconnect_state *state; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct wbint_bh_disconnect_state); + if (req == NULL) { + return NULL; + } + + ok = wbint_bh_is_connected(h); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED); + return tevent_req_post(req, ev); + } + + /* + * TODO: do a real async disconnect ... + */ + hs->domain = NULL; + hs->child = NULL; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS wbint_bh_disconnect_recv(struct tevent_req *req) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +static bool wbint_bh_ref_alloc(struct dcerpc_binding_handle *h) +{ + return true; +} + +static void wbint_bh_do_ndr_print(struct dcerpc_binding_handle *h, + ndr_flags_type ndr_flags, + const void *_struct_ptr, + const struct ndr_interface_call *call) +{ + void *struct_ptr = discard_const(_struct_ptr); + + if (DEBUGLEVEL < 10) { + return; + } + + if (ndr_flags & NDR_IN) { + ndr_print_function_debug(call->ndr_print, + call->name, + ndr_flags, + struct_ptr); + } + if (ndr_flags & NDR_OUT) { + ndr_print_function_debug(call->ndr_print, + call->name, + ndr_flags, + struct_ptr); + } +} + +static const struct dcerpc_binding_handle_ops wbint_bh_ops = { + .name = "wbint", + .is_connected = wbint_bh_is_connected, + .set_timeout = wbint_bh_set_timeout, + .raw_call_send = wbint_bh_raw_call_send, + .raw_call_recv = wbint_bh_raw_call_recv, + .disconnect_send = wbint_bh_disconnect_send, + .disconnect_recv = wbint_bh_disconnect_recv, + + .ref_alloc = wbint_bh_ref_alloc, + .do_ndr_print = wbint_bh_do_ndr_print, +}; + +static NTSTATUS make_internal_ncacn_conn(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *table, + struct dcerpc_ncacn_conn **_out) +{ + struct dcerpc_ncacn_conn *ncacn_conn = NULL; + + ncacn_conn = talloc_zero(mem_ctx, struct dcerpc_ncacn_conn); + if (ncacn_conn == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ncacn_conn->p.mem_ctx = mem_ctx; + + *_out = ncacn_conn; + + return NT_STATUS_OK; +} + +static NTSTATUS find_ncalrpc_default_endpoint(struct dcesrv_context *dce_ctx, + struct dcesrv_endpoint **ep) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct dcerpc_binding *binding = NULL; + NTSTATUS status; + + tmp_ctx = talloc_new(dce_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * Some services use a rpcint binding handle in their initialization, + * before the server is fully initialized. Search the NCALRPC endpoint + * with and without endpoint + */ + status = dcerpc_parse_binding(tmp_ctx, "ncalrpc:", &binding); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = dcesrv_find_endpoint(dce_ctx, binding, ep); + if (NT_STATUS_IS_OK(status)) { + goto out; + } + + status = dcerpc_parse_binding(tmp_ctx, "ncalrpc:[WINBIND]", &binding); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = dcesrv_find_endpoint(dce_ctx, binding, ep); + if (NT_STATUS_IS_OK(status)) { + goto out; + } + + status = dcerpc_parse_binding(tmp_ctx, "ncalrpc:[DEFAULT]", &binding); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = dcesrv_find_endpoint(dce_ctx, binding, ep); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + +out: + talloc_free(tmp_ctx); + return status; +} + +static NTSTATUS make_internal_dcesrv_connection(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *ndr_table, + struct dcerpc_ncacn_conn *ncacn_conn, + struct dcesrv_connection **_out) +{ + struct dcesrv_connection *conn = NULL; + struct dcesrv_connection_context *context = NULL; + struct dcesrv_endpoint *endpoint = NULL; + NTSTATUS status; + + conn = talloc_zero(mem_ctx, struct dcesrv_connection); + if (conn == NULL) { + return NT_STATUS_NO_MEMORY; + } + conn->dce_ctx = global_dcesrv_context(); + conn->preferred_transfer = &ndr_transfer_syntax_ndr; + conn->transport.private_data = ncacn_conn; + + status = find_ncalrpc_default_endpoint(conn->dce_ctx, &endpoint); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + conn->endpoint = endpoint; + + conn->default_auth_state = talloc_zero(conn, struct dcesrv_auth); + if (conn->default_auth_state == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + conn->default_auth_state->auth_finished = true; + + context = talloc_zero(conn, struct dcesrv_connection_context); + if (context == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + context->conn = conn; + context->context_id = 0; + context->transfer_syntax = *(conn->preferred_transfer); + context->iface = find_interface_by_syntax_id( + conn->endpoint, &ndr_table->syntax_id); + if (context->iface == NULL) { + status = NT_STATUS_RPC_INTERFACE_NOT_FOUND; + goto fail; + } + + DLIST_ADD(conn->contexts, context); + + *_out = conn; + + return NT_STATUS_OK; +fail: + talloc_free(conn); + return status; +} + +static NTSTATUS set_remote_addresses(struct dcesrv_connection *conn, + int sock) +{ + struct sockaddr_storage st = { 0 }; + struct sockaddr *sar = (struct sockaddr *)&st; + struct tsocket_address *remote = NULL; + struct tsocket_address *local = NULL; + socklen_t sa_len = sizeof(st); + NTSTATUS status; + int ret; + + ZERO_STRUCT(st); + ret = getpeername(sock, sar, &sa_len); + if (ret != 0) { + status = map_nt_error_from_unix(ret); + DBG_ERR("getpeername failed: %s\n", nt_errstr(status)); + return status; + } + + ret = tsocket_address_bsd_from_sockaddr(conn, sar, sa_len, &remote); + if (ret != 0) { + status = map_nt_error_from_unix(ret); + DBG_ERR("tsocket_address_bsd_from_sockaddr failed: %s\n", + nt_errstr(status)); + return status; + } + + ZERO_STRUCT(st); + ret = getsockname(sock, sar, &sa_len); + if (ret != 0) { + status = map_nt_error_from_unix(ret); + DBG_ERR("getsockname failed: %s\n", nt_errstr(status)); + return status; + } + + ret = tsocket_address_bsd_from_sockaddr(conn, sar, sa_len, &local); + if (ret != 0) { + status = map_nt_error_from_unix(ret); + DBG_ERR("tsocket_address_bsd_from_sockaddr failed: %s\n", + nt_errstr(status)); + return status; + } + + conn->local_address = talloc_move(conn, &local); + conn->remote_address = talloc_move(conn, &remote); + + return NT_STATUS_OK; +} + +/* initialise a wbint binding handle */ +struct dcerpc_binding_handle *wbint_binding_handle(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + struct winbindd_child *child) +{ + struct dcerpc_binding_handle *h; + struct wbint_bh_state *hs; + + h = dcerpc_binding_handle_create(mem_ctx, + &wbint_bh_ops, + NULL, + &ndr_table_winbind, + &hs, + struct wbint_bh_state, + __location__); + if (h == NULL) { + return NULL; + } + hs->domain = domain; + hs->child = child; + + return h; +} + +enum winbindd_result winbindd_dual_ndrcmd(struct winbindd_domain *domain, + struct winbindd_cli_state *state) +{ + struct dcerpc_ncacn_conn *ncacn_conn = NULL; + struct dcesrv_connection *dcesrv_conn = NULL; + struct dcesrv_call_state *dcesrv_call = NULL; + struct data_blob_list_item *rep = NULL; + struct dcesrv_context_callbacks *cb = NULL; + uint32_t opnum = state->request->data.ndrcmd; + TALLOC_CTX *mem_ctx; + NTSTATUS status; + + DBG_DEBUG("Running command %s (domain '%s')\n", + ndr_table_winbind.calls[opnum].name, + domain ? domain->name : "(null)"); + + mem_ctx = talloc_stackframe(); + if (mem_ctx == NULL) { + DBG_ERR("No memory\n"); + return WINBINDD_ERROR; + } + + status = make_internal_ncacn_conn(mem_ctx, + &ndr_table_winbind, + &ncacn_conn); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = make_internal_dcesrv_connection(ncacn_conn, + &ndr_table_winbind, + ncacn_conn, + &dcesrv_conn); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + status = set_remote_addresses(dcesrv_conn, state->sock); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + dcesrv_call = talloc_zero(dcesrv_conn, struct dcesrv_call_state); + if (dcesrv_call == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + dcesrv_call->conn = dcesrv_conn; + dcesrv_call->context = dcesrv_conn->contexts; + dcesrv_call->auth_state = dcesrv_conn->default_auth_state; + + ZERO_STRUCT(dcesrv_call->pkt); + dcesrv_call->pkt.u.bind.assoc_group_id = 0; + + cb = dcesrv_call->conn->dce_ctx->callbacks; + status = cb->assoc_group.find( + dcesrv_call, cb->assoc_group.private_data); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + ZERO_STRUCT(dcesrv_call->pkt); + dcesrv_call->pkt.u.request.opnum = opnum; + dcesrv_call->pkt.u.request.context_id = 0; + dcesrv_call->pkt.u.request.stub_and_verifier = + data_blob_const(state->request->extra_data.data, + state->request->extra_len); + + status = dcesrv_call_dispatch_local(dcesrv_call); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + rep = dcesrv_call->replies; + DLIST_REMOVE(dcesrv_call->replies, rep); + + state->response->extra_data.data = talloc_steal(state->mem_ctx, + rep->blob.data); + state->response->length += rep->blob.length; + + talloc_free(rep); + +out: + talloc_free(mem_ctx); + if (NT_STATUS_IS_OK(status)) { + return WINBINDD_OK; + } + return WINBINDD_ERROR; +} diff --git a/source3/winbindd/winbindd_dual_srv.c b/source3/winbindd/winbindd_dual_srv.c new file mode 100644 index 0000000..bbdaf6e --- /dev/null +++ b/source3/winbindd/winbindd_dual_srv.c @@ -0,0 +1,2127 @@ +/* + Unix SMB/CIFS implementation. + + In-Child server implementation of the routines defined in wbint.idl + + Copyright (C) Volker Lendecke 2009 + Copyright (C) Guenther Deschner 2009 + + 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 "winbindd/winbindd_proto.h" +#include "rpc_client/cli_pipe.h" +#include "ntdomain.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_winbind.h" +#include "librpc/gen_ndr/ndr_winbind_scompat.h" +#include "../librpc/gen_ndr/ndr_netlogon_c.h" +#include "../librpc/gen_ndr/ndr_lsa_c.h" +#include "idmap.h" +#include "../libcli/security/security.h" +#include "../libcli/auth/netlogon_creds_cli.h" +#include "passdb.h" +#include "../source4/dsdb/samdb/samdb.h" +#include "rpc_client/cli_netlogon.h" +#include "rpc_client/util_netlogon.h" +#include "libsmb/dsgetdcname.h" +#include "lib/global_contexts.h" + +NTSTATUS _wbint_Ping(struct pipes_struct *p, struct wbint_Ping *r) +{ + *r->out.out_data = r->in.in_data; + return NT_STATUS_OK; +} + +NTSTATUS _wbint_InitConnection(struct pipes_struct *p, + struct wbint_InitConnection *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + + if (r->in.dcname != NULL && strlen(r->in.dcname) > 0) { + TALLOC_FREE(domain->dcname); + domain->dcname = talloc_strdup(domain, r->in.dcname); + if (domain->dcname == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + init_dc_connection(domain, false); + + if (!domain->initialized) { + /* + * If we return error here we can't do any cached + * authentication, but we may be in disconnected mode and can't + * initialize correctly. Do what the previous code did and just + * return without initialization, once we go online we'll + * re-initialize. + */ + DBG_INFO("%s returning without initialization online = %d\n", + domain->name, (int)domain->online); + } + + *r->out.name = talloc_strdup(p->mem_ctx, domain->name); + if (*r->out.name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (domain->alt_name != NULL) { + *r->out.alt_name = talloc_strdup(p->mem_ctx, domain->alt_name); + if (*r->out.alt_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + r->out.sid = dom_sid_dup(p->mem_ctx, &domain->sid); + if (r->out.sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *r->out.flags = 0; + if (domain->native_mode) { + *r->out.flags |= WB_DOMINFO_DOMAIN_NATIVE; + } + if (domain->active_directory) { + *r->out.flags |= WB_DOMINFO_DOMAIN_AD; + } + if (domain->primary) { + *r->out.flags |= WB_DOMINFO_DOMAIN_PRIMARY; + } + + return NT_STATUS_OK; +} + +bool reset_cm_connection_on_error(struct winbindd_domain *domain, + struct dcerpc_binding_handle *b, + NTSTATUS status) +{ + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) || + NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR) || + NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) { + invalidate_cm_connection(domain); + domain->conn.netlogon_force_reauth = true; + return true; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR)) + { + invalidate_cm_connection(domain); + /* We invalidated the connection. */ + return true; + } + + if (b != NULL && !dcerpc_binding_handle_is_connected(b)) { + invalidate_cm_connection(domain); + return true; + } + + return false; +} + +NTSTATUS _wbint_LookupSid(struct pipes_struct *p, struct wbint_LookupSid *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + char *dom_name; + char *name; + enum lsa_SidType type; + NTSTATUS status; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = wb_cache_sid_to_name(domain, p->mem_ctx, r->in.sid, + &dom_name, &name, &type); + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *r->out.domain = dom_name; + *r->out.name = name; + *r->out.type = type; + return NT_STATUS_OK; +} + +NTSTATUS _wbint_LookupSids(struct pipes_struct *p, struct wbint_LookupSids *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + struct lsa_RefDomainList *domains = r->out.domains; + NTSTATUS status; + bool retry = false; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + /* + * This breaks the winbindd_domain->methods abstraction: This + * is only called for remote domains, and both winbindd_msrpc + * and winbindd_ad call into lsa_lookupsids anyway. Caching is + * done at the wbint RPC layer. + */ +again: + status = rpc_lookup_sids(p->mem_ctx, domain, r->in.sids, + &domains, &r->out.names); + + if (domains != NULL) { + r->out.domains = domains; + } + + if (!retry && reset_cm_connection_on_error(domain, NULL, status)) { + retry = true; + goto again; + } + + return status; +} + +NTSTATUS _wbint_LookupName(struct pipes_struct *p, struct wbint_LookupName *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS status; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = wb_cache_name_to_sid(domain, p->mem_ctx, r->in.domain, + r->in.name, r->in.flags, + r->out.sid, r->out.type); + reset_cm_connection_on_error(domain, NULL, status); + return status; +} + +NTSTATUS _wbint_Sids2UnixIDs(struct pipes_struct *p, + struct wbint_Sids2UnixIDs *r) +{ + uint32_t i; + + struct lsa_DomainInfo *d; + struct wbint_TransID *ids; + uint32_t num_ids; + + struct id_map **id_map_ptrs = NULL; + struct idmap_domain *dom; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + if (r->in.domains->count != 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + d = &r->in.domains->domains[0]; + ids = r->in.ids->ids; + num_ids = r->in.ids->num_ids; + + dom = idmap_find_domain_with_sid(d->name.string, d->sid); + if (dom == NULL) { + struct dom_sid_buf buf; + DEBUG(10, ("idmap domain %s:%s not found\n", + d->name.string, + dom_sid_str_buf(d->sid, &buf))); + + for (i=0; i<num_ids; i++) { + + ids[i].xid = (struct unixid) { + .id = UINT32_MAX, + .type = ID_TYPE_NOT_SPECIFIED + }; + } + + return NT_STATUS_OK; + } + + id_map_ptrs = id_map_ptrs_init(talloc_tos(), num_ids); + if (id_map_ptrs == NULL) { + goto nomem; + } + + /* + * Convert the input data into a list of id_map structs + * suitable for handing in to the idmap sids_to_unixids + * method. + */ + + for (i=0; i<num_ids; i++) { + struct id_map *m = id_map_ptrs[i]; + + sid_compose(m->sid, d->sid, ids[i].rid); + m->status = ID_UNKNOWN; + m->xid = (struct unixid) { .type = ids[i].type_hint }; + } + + status = dom->methods->sids_to_unixids(dom, id_map_ptrs); + + if (NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) { + /* + * This is okay. We need to transfer the mapped ones + * up to our caller. The individual mappings carry the + * information whether they are mapped or not. + */ + status = NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("sids_to_unixids returned %s\n", + nt_errstr(status))); + goto done; + } + + /* + * Extract the results for handing them back to the caller. + */ + + for (i=0; i<num_ids; i++) { + struct id_map *m = id_map_ptrs[i]; + + if (m->status == ID_REQUIRE_TYPE) { + ids[i].xid.id = UINT32_MAX; + ids[i].xid.type = ID_TYPE_WB_REQUIRE_TYPE; + continue; + } + + if (!idmap_unix_id_is_in_range(m->xid.id, dom)) { + DBG_DEBUG("id %"PRIu32" is out of range " + "%"PRIu32"-%"PRIu32" for domain %s\n", + m->xid.id, dom->low_id, dom->high_id, + dom->name); + m->status = ID_UNMAPPED; + } + + if (m->status == ID_MAPPED) { + ids[i].xid = m->xid; + } else { + ids[i].xid.id = UINT32_MAX; + ids[i].xid.type = ID_TYPE_NOT_SPECIFIED; + } + } + + goto done; +nomem: + status = NT_STATUS_NO_MEMORY; +done: + TALLOC_FREE(id_map_ptrs); + return status; +} + +NTSTATUS _wbint_UnixIDs2Sids(struct pipes_struct *p, + struct wbint_UnixIDs2Sids *r) +{ + struct id_map **maps; + NTSTATUS status; + uint32_t i; + + maps = id_map_ptrs_init(talloc_tos(), r->in.num_ids); + if (maps == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<r->in.num_ids; i++) { + maps[i]->status = ID_UNKNOWN; + maps[i]->xid = r->in.xids[i]; + } + + status = idmap_backend_unixids_to_sids(maps, r->in.domain_name, + r->in.domain_sid); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(maps); + return status; + } + + for (i=0; i<r->in.num_ids; i++) { + if (maps[i]->status == ID_MAPPED) { + r->out.xids[i] = maps[i]->xid; + sid_copy(&r->out.sids[i], maps[i]->sid); + } else { + r->out.sids[i] = (struct dom_sid) { 0 }; + } + } + + TALLOC_FREE(maps); + + return NT_STATUS_OK; +} + +NTSTATUS _wbint_AllocateUid(struct pipes_struct *p, struct wbint_AllocateUid *r) +{ + struct unixid xid; + NTSTATUS status; + + status = idmap_allocate_uid(&xid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *r->out.uid = xid.id; + return NT_STATUS_OK; +} + +NTSTATUS _wbint_AllocateGid(struct pipes_struct *p, struct wbint_AllocateGid *r) +{ + struct unixid xid; + NTSTATUS status; + + status = idmap_allocate_gid(&xid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + *r->out.gid = xid.id; + return NT_STATUS_OK; +} + +NTSTATUS _wbint_GetNssInfo(struct pipes_struct *p, struct wbint_GetNssInfo *r) +{ + struct idmap_domain *domain; + NTSTATUS status; + + domain = idmap_find_domain(r->in.info->domain_name); + if ((domain == NULL) || (domain->query_user == NULL)) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = domain->query_user(domain, r->in.info); + return status; +} + +NTSTATUS _wbint_LookupUserAliases(struct pipes_struct *p, + struct wbint_LookupUserAliases *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS status; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = wb_cache_lookup_useraliases(domain, p->mem_ctx, + r->in.sids->num_sids, + r->in.sids->sids, + &r->out.rids->num_rids, + &r->out.rids->rids); + reset_cm_connection_on_error(domain, NULL, status); + return status; +} + +NTSTATUS _wbint_LookupUserGroups(struct pipes_struct *p, + struct wbint_LookupUserGroups *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS status; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = wb_cache_lookup_usergroups(domain, p->mem_ctx, r->in.sid, + &r->out.sids->num_sids, + &r->out.sids->sids); + reset_cm_connection_on_error(domain, NULL, status); + return status; +} + +NTSTATUS _wbint_QuerySequenceNumber(struct pipes_struct *p, + struct wbint_QuerySequenceNumber *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS status; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = wb_cache_sequence_number(domain, r->out.sequence); + reset_cm_connection_on_error(domain, NULL, status); + return status; +} + +NTSTATUS _wbint_LookupGroupMembers(struct pipes_struct *p, + struct wbint_LookupGroupMembers *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + uint32_t i, num_names; + struct dom_sid *sid_mem; + char **names; + uint32_t *name_types; + NTSTATUS status; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = wb_cache_lookup_groupmem(domain, p->mem_ctx, r->in.sid, + r->in.type, &num_names, &sid_mem, + &names, &name_types); + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->out.members->num_principals = num_names; + r->out.members->principals = talloc_array( + r->out.members, struct wbint_Principal, num_names); + if (r->out.members->principals == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_names; i++) { + struct wbint_Principal *m = &r->out.members->principals[i]; + sid_copy(&m->sid, &sid_mem[i]); + m->name = talloc_move(r->out.members->principals, &names[i]); + m->type = (enum lsa_SidType)name_types[i]; + } + + return NT_STATUS_OK; +} + +NTSTATUS _wbint_LookupAliasMembers(struct pipes_struct *p, + struct wbint_LookupAliasMembers *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS status; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + status = wb_cache_lookup_aliasmem(domain, + p->mem_ctx, + r->in.sid, + r->in.type, + &r->out.sids->num_sids, + &r->out.sids->sids); + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +NTSTATUS _wbint_QueryGroupList(struct pipes_struct *p, + struct wbint_QueryGroupList *r) +{ + TALLOC_CTX *frame = NULL; + struct winbindd_domain *domain = wb_child_domain(); + uint32_t i; + uint32_t num_local_groups = 0; + struct wb_acct_info *local_groups = NULL; + uint32_t num_dom_groups = 0; + struct wb_acct_info *dom_groups = NULL; + uint32_t ti = 0; + uint64_t num_total = 0; + struct wbint_Principal *result; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + bool include_local_groups = false; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + frame = talloc_stackframe(); + + switch (lp_server_role()) { + case ROLE_ACTIVE_DIRECTORY_DC: + if (domain->internal) { + /* + * we want to include local groups + * for BUILTIN and WORKGROUP + */ + include_local_groups = true; + } + break; + case ROLE_DOMAIN_MEMBER: + /* + * This is needed for GETGRENT to show also e.g. BUILTIN/users. + * Otherwise the test_membership_user (smbtorture + * local.nss.membership) would fail (getgrouplist() would + * reports BUILTIN/users). + */ + if (domain->internal) { + /* + * we want to include local groups + * for BUILTIN and LOCALSAM + */ + include_local_groups = true; + } + break; + default: + /* + * We might include local groups in more + * setups later, but that requires more work + * elsewhere. + */ + break; + } + + if (include_local_groups) { + status = wb_cache_enum_local_groups(domain, frame, + &num_local_groups, + &local_groups); + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + } + + status = wb_cache_enum_dom_groups(domain, frame, + &num_dom_groups, + &dom_groups); + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + num_total = num_local_groups + num_dom_groups; + if (num_total > UINT32_MAX) { + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + result = talloc_array(frame, struct wbint_Principal, num_total); + if (result == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + for (i = 0; i < num_local_groups; i++) { + struct wb_acct_info *lg = &local_groups[i]; + struct wbint_Principal *rg = &result[ti++]; + + sid_compose(&rg->sid, &domain->sid, lg->rid); + rg->type = SID_NAME_ALIAS; + rg->name = talloc_strdup(result, lg->acct_name); + if (rg->name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + num_local_groups = 0; + + for (i = 0; i < num_dom_groups; i++) { + struct wb_acct_info *dg = &dom_groups[i]; + struct wbint_Principal *rg = &result[ti++]; + + sid_compose(&rg->sid, &domain->sid, dg->rid); + rg->type = SID_NAME_DOM_GRP; + rg->name = talloc_strdup(result, dg->acct_name); + if (rg->name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + num_dom_groups = 0; + + r->out.groups->num_principals = ti; + r->out.groups->principals = talloc_move(r->out.groups, &result); + + status = NT_STATUS_OK; +out: + TALLOC_FREE(frame); + return status; +} + +NTSTATUS _wbint_QueryUserRidList(struct pipes_struct *p, + struct wbint_QueryUserRidList *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS status; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + /* + * Right now this is overkill. We should add a backend call + * just querying the rids. + */ + + status = wb_cache_query_user_list(domain, p->mem_ctx, + &r->out.rids->rids); + reset_cm_connection_on_error(domain, NULL, status); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->out.rids->num_rids = talloc_array_length(r->out.rids->rids); + + return NT_STATUS_OK; +} + +NTSTATUS _wbint_DsGetDcName(struct pipes_struct *p, struct wbint_DsGetDcName *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + struct rpc_pipe_client *netlogon_pipe; + struct netr_DsRGetDCNameInfo *dc_info; + NTSTATUS status; + WERROR werr; + unsigned int orig_timeout; + struct dcerpc_binding_handle *b; + bool retry = false; + bool try_dsrgetdcname = false; + + if (domain == NULL) { + return dsgetdcname(p->mem_ctx, global_messaging_context(), + r->in.domain_name, r->in.domain_guid, + r->in.site_name ? r->in.site_name : "", + r->in.flags, + r->out.dc_info); + } + + if (domain->active_directory) { + try_dsrgetdcname = true; + } + +reconnect: + status = cm_connect_netlogon(domain, &netlogon_pipe); + + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Can't contact the NETLOGON pipe\n")); + return status; + } + + b = netlogon_pipe->binding_handle; + + /* This call can take a long time - allow the server to time out. + 35 seconds should do it. */ + + orig_timeout = rpccli_set_timeout(netlogon_pipe, 35000); + + if (try_dsrgetdcname) { + status = dcerpc_netr_DsRGetDCName(b, + p->mem_ctx, domain->dcname, + r->in.domain_name, NULL, r->in.domain_guid, + r->in.flags, r->out.dc_info, &werr); + if (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(werr)) { + goto done; + } + if (!retry && + reset_cm_connection_on_error(domain, NULL, status)) + { + retry = true; + goto reconnect; + } + try_dsrgetdcname = false; + retry = false; + } + + /* + * Fallback to less capable methods + */ + + dc_info = talloc_zero(r->out.dc_info, struct netr_DsRGetDCNameInfo); + if (dc_info == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + if (r->in.flags & DS_PDC_REQUIRED) { + status = dcerpc_netr_GetDcName(b, + p->mem_ctx, domain->dcname, + r->in.domain_name, &dc_info->dc_unc, &werr); + } else { + status = dcerpc_netr_GetAnyDCName(b, + p->mem_ctx, domain->dcname, + r->in.domain_name, &dc_info->dc_unc, &werr); + } + + if (!retry && reset_cm_connection_on_error(domain, b, status)) { + retry = true; + goto reconnect; + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("dcerpc_netr_Get[Any]DCName failed: %s\n", + nt_errstr(status))); + goto done; + } + if (!W_ERROR_IS_OK(werr)) { + DEBUG(10, ("dcerpc_netr_Get[Any]DCName failed: %s\n", + win_errstr(werr))); + status = werror_to_ntstatus(werr); + goto done; + } + + *r->out.dc_info = dc_info; + status = NT_STATUS_OK; + +done: + /* And restore our original timeout. */ + rpccli_set_timeout(netlogon_pipe, orig_timeout); + + return status; +} + +NTSTATUS _wbint_LookupRids(struct pipes_struct *p, struct wbint_LookupRids *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + char *domain_name; + char **names; + enum lsa_SidType *types; + struct wbint_Principal *result; + NTSTATUS status; + uint32_t i; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = wb_cache_rids_to_names(domain, talloc_tos(), r->in.domain_sid, + r->in.rids->rids, r->in.rids->num_rids, + &domain_name, &names, &types); + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) { + return status; + } + + *r->out.domain_name = talloc_move(r->out.domain_name, &domain_name); + + result = talloc_array(p->mem_ctx, struct wbint_Principal, + r->in.rids->num_rids); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<r->in.rids->num_rids; i++) { + sid_compose(&result[i].sid, r->in.domain_sid, + r->in.rids->rids[i]); + result[i].type = types[i]; + result[i].name = talloc_move(result, &names[i]); + } + TALLOC_FREE(types); + TALLOC_FREE(names); + + r->out.names->num_principals = r->in.rids->num_rids; + r->out.names->principals = result; + return NT_STATUS_OK; +} + +NTSTATUS _wbint_CheckMachineAccount(struct pipes_struct *p, + struct wbint_CheckMachineAccount *r) +{ + struct winbindd_domain *domain; + int num_retries = 0; + NTSTATUS status; + + domain = wb_child_domain(); + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + +again: + invalidate_cm_connection(domain); + domain->conn.netlogon_force_reauth = true; + + { + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + } + + /* There is a race condition between fetching the trust account + password and the periodic machine password change. So it's + possible that the trust account password has been changed on us. + We are returned NT_STATUS_ACCESS_DENIED if this happens. */ + +#define MAX_RETRIES 3 + + if ((num_retries < MAX_RETRIES) + && NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + num_retries++; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("could not open handle to NETLOGON pipe\n")); + goto done; + } + + /* Pass back result code - zero for success, other values for + specific failures. */ + + DEBUG(3,("domain %s secret is %s\n", domain->name, + NT_STATUS_IS_OK(status) ? "good" : "bad")); + + done: + DEBUG(NT_STATUS_IS_OK(status) ? 5 : 2, + ("Checking the trust account password for domain %s returned %s\n", + domain->name, nt_errstr(status))); + + return status; +} + +NTSTATUS _wbint_ChangeMachineAccount(struct pipes_struct *p, + struct wbint_ChangeMachineAccount *r) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + struct winbindd_domain *domain; + NTSTATUS status; + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + + domain = wb_child_domain(); + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + if (r->in.dcname != NULL && r->in.dcname[0] != '\0') { + invalidate_cm_connection(domain); + TALLOC_FREE(domain->dcname); + + domain->dcname = talloc_strdup(domain, r->in.dcname); + if (domain->dcname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + domain->force_dc = true; + + DBG_NOTICE("attempt connection to change trust account " + "password for %s at %s\n", + domain->name, domain->dcname); + } + + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("could not open handle to NETLOGON pipe\n")); + goto done; + } + + status = trust_pw_change(netlogon_creds_ctx, + msg_ctx, + netlogon_pipe->binding_handle, + domain->name, + domain->dcname, + true); /* force */ + + /* Pass back result code - zero for success, other values for + specific failures. */ + + DEBUG(3,("domain %s secret %s\n", domain->name, + NT_STATUS_IS_OK(status) ? "changed" : "unchanged")); + + done: + DEBUG(NT_STATUS_IS_OK(status) ? 5 : + domain->force_dc ? 0 : 2, + ("Changing the trust account password for domain %s at %s " + "(forced: %s) returned %s\n", + domain->name, domain->dcname, domain->force_dc ? "yes" : "no", + nt_errstr(status))); + domain->force_dc = false; + + return status; +} + +NTSTATUS _wbint_PingDc(struct pipes_struct *p, struct wbint_PingDc *r) +{ + NTSTATUS status; + struct winbindd_domain *domain; + struct rpc_pipe_client *netlogon_pipe; + union netr_CONTROL_QUERY_INFORMATION info; + WERROR werr; + fstring logon_server; + struct dcerpc_binding_handle *b; + bool retry = false; + + domain = wb_child_domain(); + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + +reconnect: + status = cm_connect_netlogon(domain, &netlogon_pipe); + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("could not open handle to NETLOGON pipe: %s\n", + nt_errstr(status))); + return status; + } + + b = netlogon_pipe->binding_handle; + + fstr_sprintf(logon_server, "\\\\%s", domain->dcname); + *r->out.dcname = talloc_strdup(p->mem_ctx, domain->dcname); + if (*r->out.dcname == NULL) { + DEBUG(2, ("Could not allocate memory\n")); + return NT_STATUS_NO_MEMORY; + } + + /* + * This provokes a WERR_NOT_SUPPORTED error message. This is + * documented in the wspp docs. I could not get a successful + * call to work, but the main point here is testing that the + * netlogon pipe works. + */ + status = dcerpc_netr_LogonControl(b, p->mem_ctx, + logon_server, NETLOGON_CONTROL_QUERY, + 2, &info, &werr); + + if (!retry && reset_cm_connection_on_error(domain, b, status)) { + retry = true; + goto reconnect; + } + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("dcerpc_netr_LogonControl failed: %s\n", + nt_errstr(status))); + return status; + } + + if (!W_ERROR_EQUAL(werr, WERR_NOT_SUPPORTED)) { + DEBUG(2, ("dcerpc_netr_LogonControl returned %s, expected " + "WERR_NOT_SUPPORTED\n", + win_errstr(werr))); + return werror_to_ntstatus(werr); + } + + DEBUG(5, ("winbindd_dual_ping_dc succeeded\n")); + return NT_STATUS_OK; +} + +NTSTATUS _winbind_DsrUpdateReadOnlyServerDnsRecords(struct pipes_struct *p, + struct winbind_DsrUpdateReadOnlyServerDnsRecords *r) +{ + struct winbindd_domain *domain; + NTSTATUS status; + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + struct dcerpc_binding_handle *b = NULL; + bool retry = false; + + domain = wb_child_domain(); + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + +reconnect: + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("could not open handle to NETLOGON pipe\n")); + goto done; + } + + b = netlogon_pipe->binding_handle; + + status = netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords(netlogon_creds_ctx, + netlogon_pipe->binding_handle, + r->in.site_name, + r->in.dns_ttl, + r->in.dns_names); + + if (!retry && reset_cm_connection_on_error(domain, b, status)) { + retry = true; + goto reconnect; + } + + /* Pass back result code - zero for success, other values for + specific failures. */ + + DEBUG(3,("DNS records for domain %s %s\n", domain->name, + NT_STATUS_IS_OK(status) ? "changed" : "unchanged")); + + done: + DEBUG(NT_STATUS_IS_OK(status) ? 5 : 2, + ("Update of DNS records via RW DC %s returned %s\n", + domain->name, nt_errstr(status))); + + return status; +} + +NTSTATUS _winbind_SamLogon(struct pipes_struct *p, + struct winbind_SamLogon *r) +{ + struct dcesrv_call_state *dce_call = p->dce_call; + struct dcesrv_connection *dcesrv_conn = dce_call->conn; + const struct tsocket_address *local_address = + dcesrv_connection_get_local_address(dcesrv_conn); + const struct tsocket_address *remote_address = + dcesrv_connection_get_remote_address(dcesrv_conn); + struct winbindd_domain *domain; + NTSTATUS status; + struct netr_IdentityInfo *identity_info = NULL; + DATA_BLOB lm_response, nt_response; + DATA_BLOB challenge = data_blob_null; + uint32_t flags = 0; + uint16_t validation_level; + union netr_Validation *validation = NULL; + bool interactive = false; + + /* + * Make sure we start with authoritative=true, + * it will only set to false if we don't know the + * domain. + */ + r->out.authoritative = true; + + domain = wb_child_domain(); + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + switch (r->in.validation_level) { + case 3: + case 6: + break; + default: + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + if (r->in.logon.password == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + interactive = true; + identity_info = &r->in.logon.password->identity_info; + + challenge = data_blob_null; + lm_response = data_blob_talloc(p->mem_ctx, + r->in.logon.password->lmpassword.hash, + sizeof(r->in.logon.password->lmpassword.hash)); + nt_response = data_blob_talloc(p->mem_ctx, + r->in.logon.password->ntpassword.hash, + sizeof(r->in.logon.password->ntpassword.hash)); + break; + + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + if (r->in.logon.network == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + interactive = false; + identity_info = &r->in.logon.network->identity_info; + + challenge = data_blob_talloc(p->mem_ctx, + r->in.logon.network->challenge, + 8); + lm_response = data_blob_talloc(p->mem_ctx, + r->in.logon.network->lm.data, + r->in.logon.network->lm.length); + nt_response = data_blob_talloc(p->mem_ctx, + r->in.logon.network->nt.data, + r->in.logon.network->nt.length); + break; + + case NetlogonGenericInformation: + if (r->in.logon.generic == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + identity_info = &r->in.logon.generic->identity_info; + /* + * Not implemented here... + */ + return NT_STATUS_REQUEST_NOT_ACCEPTED; + + default: + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + status = winbind_dual_SamLogon(domain, p->mem_ctx, + interactive, + identity_info->parameter_control, + identity_info->account_name.string, + identity_info->domain_name.string, + identity_info->workstation.string, + identity_info->logon_id, + "SamLogon", + 0, + challenge, + lm_response, nt_response, + remote_address, + local_address, + &r->out.authoritative, + true, /* skip_sam */ + &flags, + &validation_level, + &validation); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + switch (r->in.validation_level) { + case 3: + status = map_validation_to_info3(p->mem_ctx, + validation_level, + validation, + &r->out.validation.sam3); + TALLOC_FREE(validation); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return NT_STATUS_OK; + case 6: + status = map_validation_to_info6(p->mem_ctx, + validation_level, + validation, + &r->out.validation.sam6); + TALLOC_FREE(validation); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return NT_STATUS_OK; + } + + smb_panic(__location__); + return NT_STATUS_INTERNAL_ERROR; +} + +static WERROR _winbind_LogonControl_REDISCOVER(struct pipes_struct *p, + struct winbindd_domain *domain, + struct winbind_LogonControl *r) +{ + NTSTATUS status; + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + struct netr_NETLOGON_INFO_2 *info2 = NULL; + WERROR check_result = WERR_INTERNAL_ERROR; + + info2 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_2); + if (info2 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (domain->internal) { + check_result = WERR_OK; + goto check_return; + } + + /* + * For now we just force a reconnect + * + * TODO: take care of the optional '\dcname' + */ + invalidate_cm_connection(domain); + domain->conn.netlogon_force_reauth = true; + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + reset_cm_connection_on_error(domain, NULL, status); + if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + status = NT_STATUS_NO_LOGON_SERVERS; + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("%s: domain[%s/%s] cm_connect_netlogon() returned %s\n", + __func__, domain->name, domain->alt_name, + nt_errstr(status))); + /* + * Here we return a top level error! + * This is different than TC_QUERY or TC_VERIFY. + */ + return ntstatus_to_werror(status); + } + check_result = WERR_OK; + +check_return: + info2->pdc_connection_status = WERR_OK; + if (domain->dcname != NULL) { + info2->flags |= NETLOGON_HAS_IP; + info2->flags |= NETLOGON_HAS_TIMESERV; + info2->trusted_dc_name = talloc_asprintf(info2, "\\\\%s", + domain->dcname); + if (info2->trusted_dc_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } else { + info2->trusted_dc_name = talloc_strdup(info2, ""); + if (info2->trusted_dc_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + info2->tc_connection_status = check_result; + + if (!W_ERROR_IS_OK(info2->pdc_connection_status) || + !W_ERROR_IS_OK(info2->tc_connection_status)) + { + DEBUG(2, ("%s: domain[%s/%s] dcname[%s] " + "pdc_connection[%s] tc_connection[%s]\n", + __func__, domain->name, domain->alt_name, + domain->dcname, + win_errstr(info2->pdc_connection_status), + win_errstr(info2->tc_connection_status))); + } + + r->out.query->info2 = info2; + + DEBUG(5, ("%s: succeeded.\n", __func__)); + return WERR_OK; +} + +static WERROR _winbind_LogonControl_TC_QUERY(struct pipes_struct *p, + struct winbindd_domain *domain, + struct winbind_LogonControl *r) +{ + NTSTATUS status; + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + struct netr_NETLOGON_INFO_2 *info2 = NULL; + WERROR check_result = WERR_INTERNAL_ERROR; + + info2 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_2); + if (info2 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (domain->internal) { + check_result = WERR_OK; + goto check_return; + } + + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + reset_cm_connection_on_error(domain, NULL, status); + if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + status = NT_STATUS_NO_LOGON_SERVERS; + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("could not open handle to NETLOGON pipe: %s\n", + nt_errstr(status))); + check_result = ntstatus_to_werror(status); + goto check_return; + } + check_result = WERR_OK; + +check_return: + info2->pdc_connection_status = WERR_OK; + if (domain->dcname != NULL) { + info2->flags |= NETLOGON_HAS_IP; + info2->flags |= NETLOGON_HAS_TIMESERV; + info2->trusted_dc_name = talloc_asprintf(info2, "\\\\%s", + domain->dcname); + if (info2->trusted_dc_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } else { + info2->trusted_dc_name = talloc_strdup(info2, ""); + if (info2->trusted_dc_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + info2->tc_connection_status = check_result; + + if (!W_ERROR_IS_OK(info2->pdc_connection_status) || + !W_ERROR_IS_OK(info2->tc_connection_status)) + { + DEBUG(2, ("%s: domain[%s/%s] dcname[%s] " + "pdc_connection[%s] tc_connection[%s]\n", + __func__, domain->name, domain->alt_name, + domain->dcname, + win_errstr(info2->pdc_connection_status), + win_errstr(info2->tc_connection_status))); + } + + r->out.query->info2 = info2; + + DEBUG(5, ("%s: succeeded.\n", __func__)); + return WERR_OK; +} + +static WERROR _winbind_LogonControl_TC_VERIFY(struct pipes_struct *p, + struct winbindd_domain *domain, + struct winbind_LogonControl *r) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + NTSTATUS result; + struct lsa_String trusted_domain_name = {}; + struct lsa_StringLarge trusted_domain_name_l = {}; + struct rpc_pipe_client *local_lsa_pipe = NULL; + struct policy_handle local_lsa_policy = {}; + struct dcerpc_binding_handle *local_lsa = NULL; + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + struct cli_credentials *creds = NULL; + struct samr_Password *cur_nt_hash = NULL; + uint32_t trust_attributes = 0; + struct samr_Password new_owf_password = {}; + bool cmp_new = false; + struct samr_Password old_owf_password = {}; + bool cmp_old = false; + const struct lsa_TrustDomainInfoInfoEx *local_tdo = NULL; + bool fetch_fti = false; + struct lsa_ForestTrustInformation *new_fti = NULL; + struct netr_TrustInfo *trust_info = NULL; + struct netr_NETLOGON_INFO_2 *info2 = NULL; + struct dcerpc_binding_handle *b = NULL; + WERROR check_result = WERR_INTERNAL_ERROR; + WERROR verify_result = WERR_INTERNAL_ERROR; + bool retry = false; + + trusted_domain_name.string = domain->name; + trusted_domain_name_l.string = domain->name; + + info2 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_2); + if (info2 == NULL) { + TALLOC_FREE(frame); + return WERR_NOT_ENOUGH_MEMORY; + } + + if (domain->internal) { + check_result = WERR_OK; + goto check_return; + } + + status = pdb_get_trust_credentials(domain->name, + domain->alt_name, + frame, + &creds); + if (NT_STATUS_IS_OK(status)) { + cur_nt_hash = cli_credentials_get_nt_hash(creds, frame); + TALLOC_FREE(creds); + } + + if (!domain->primary) { + union lsa_TrustedDomainInfo *tdi = NULL; + + status = open_internal_lsa_conn(frame, &local_lsa_pipe, + &local_lsa_policy); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: open_internal_lsa_conn() failed - %s\n", + __location__, __func__, nt_errstr(status))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + local_lsa = local_lsa_pipe->binding_handle; + + status = dcerpc_lsa_QueryTrustedDomainInfoByName(local_lsa, frame, + &local_lsa_policy, + &trusted_domain_name, + LSA_TRUSTED_DOMAIN_INFO_INFO_EX, + &tdi, &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName(%s) failed - %s\n", + __location__, __func__, domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + if (NT_STATUS_EQUAL(result, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + DEBUG(1,("%s:%s: domain[%s] not found via LSA, might be removed already.\n", + __location__, __func__, domain->name)); + TALLOC_FREE(frame); + return WERR_NO_SUCH_DOMAIN; + } + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName(%s) returned %s\n", + __location__, __func__, domain->name, nt_errstr(result))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + if (tdi == NULL) { + DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName() " + "returned no trusted domain information\n", + __location__, __func__)); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + + local_tdo = &tdi->info_ex; + trust_attributes = local_tdo->trust_attributes; + } + + if (trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + struct lsa_ForestTrustInformation *old_fti = NULL; + + status = dcerpc_lsa_lsaRQueryForestTrustInformation(local_lsa, frame, + &local_lsa_policy, + &trusted_domain_name, + LSA_FOREST_TRUST_DOMAIN_INFO, + &old_fti, &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation(%s) failed %s\n", + __location__, __func__, domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_FOUND)) { + DEBUG(2,("%s: no forest trust information available for domain[%s] yet.\n", + __func__, domain->name)); + old_fti = NULL; + fetch_fti = true; + result = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation(%s) returned %s\n", + __location__, __func__, domain->name, nt_errstr(result))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + + TALLOC_FREE(old_fti); + } + +reconnect: + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + reset_cm_connection_on_error(domain, NULL, status); + if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + status = NT_STATUS_NO_LOGON_SERVERS; + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("could not open handle to NETLOGON pipe: %s\n", + nt_errstr(status))); + check_result = ntstatus_to_werror(status); + goto check_return; + } + check_result = WERR_OK; + b = netlogon_pipe->binding_handle; + + if (cur_nt_hash == NULL) { + verify_result = WERR_NO_TRUST_LSA_SECRET; + goto verify_return; + } + + if (fetch_fti) { + status = netlogon_creds_cli_GetForestTrustInformation(netlogon_creds_ctx, + b, frame, + &new_fti); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + status = NT_STATUS_NOT_SUPPORTED; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + new_fti = NULL; + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + if (!retry && + reset_cm_connection_on_error(domain, b, status)) + { + retry = true; + goto reconnect; + } + DEBUG(2, ("netlogon_creds_cli_GetForestTrustInformation(%s)" + "failed: %s\n", + domain->name, nt_errstr(status))); + check_result = ntstatus_to_werror(status); + goto check_return; + } + } + + if (new_fti != NULL) { + struct lsa_ForestTrustInformation old_fti = {}; + struct lsa_ForestTrustInformation *merged_fti = NULL; + struct lsa_ForestTrustCollisionInfo *collision_info = NULL; + + status = dsdb_trust_merge_forest_info(frame, local_tdo, + &old_fti, new_fti, + &merged_fti); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: dsdb_trust_merge_forest_info(%s) failed %s\n", + __location__, __func__, + domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return ntstatus_to_werror(status); + } + + status = dcerpc_lsa_lsaRSetForestTrustInformation(local_lsa, frame, + &local_lsa_policy, + &trusted_domain_name_l, + LSA_FOREST_TRUST_DOMAIN_INFO, + merged_fti, + 0, /* check_only=0 => store it! */ + &collision_info, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: local_lsa.lsaRSetForestTrustInformation(%s) failed %s\n", + __location__, __func__, domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("%s:%s: local_lsa.lsaRSetForestTrustInformation(%s) returned %s\n", + __location__, __func__, domain->name, nt_errstr(result))); + TALLOC_FREE(frame); + return ntstatus_to_werror(result); + } + } + + status = netlogon_creds_cli_ServerGetTrustInfo(netlogon_creds_ctx, + b, frame, + &new_owf_password, + &old_owf_password, + &trust_info); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + status = NT_STATUS_NOT_SUPPORTED; + } + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) { + DEBUG(5, ("netlogon_creds_cli_ServerGetTrustInfo failed: %s\n", + nt_errstr(status))); + verify_result = WERR_OK; + goto verify_return; + } + if (!NT_STATUS_IS_OK(status)) { + if (!retry && reset_cm_connection_on_error(domain, b, status)) { + retry = true; + goto reconnect; + } + DEBUG(2, ("netlogon_creds_cli_ServerGetTrustInfo failed: %s\n", + nt_errstr(status))); + + if (!dcerpc_binding_handle_is_connected(b)) { + check_result = ntstatus_to_werror(status); + goto check_return; + } else { + verify_result = ntstatus_to_werror(status); + goto verify_return; + } + } + + if (trust_info != NULL && trust_info->count >= 1) { + uint32_t diff = trust_info->data[0] ^ trust_attributes; + + if (diff & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + verify_result = WERR_DOMAIN_TRUST_INCONSISTENT; + goto verify_return; + } + } + + cmp_new = mem_equal_const_time(new_owf_password.hash, + cur_nt_hash->hash, + sizeof(cur_nt_hash->hash)); + cmp_old = mem_equal_const_time(old_owf_password.hash, + cur_nt_hash->hash, + sizeof(cur_nt_hash->hash)); + if (!cmp_new && !cmp_old) { + DEBUG(1,("%s:Error: credentials for domain[%s/%s] doesn't match " + "any password known to dcname[%s]\n", + __func__, domain->name, domain->alt_name, + domain->dcname)); + verify_result = WERR_WRONG_PASSWORD; + goto verify_return; + } + + if (!cmp_new) { + DEBUG(2,("%s:Warning: credentials for domain[%s/%s] only match " + "against the old password known to dcname[%s]\n", + __func__, domain->name, domain->alt_name, + domain->dcname)); + } + + verify_result = WERR_OK; + goto verify_return; + +check_return: + verify_result = check_result; +verify_return: + info2->flags |= NETLOGON_VERIFY_STATUS_RETURNED; + info2->pdc_connection_status = verify_result; + if (domain->dcname != NULL) { + info2->flags |= NETLOGON_HAS_IP; + info2->flags |= NETLOGON_HAS_TIMESERV; + info2->trusted_dc_name = talloc_asprintf(info2, "\\\\%s", + domain->dcname); + if (info2->trusted_dc_name == NULL) { + TALLOC_FREE(frame); + return WERR_NOT_ENOUGH_MEMORY; + } + } else { + info2->trusted_dc_name = talloc_strdup(info2, ""); + if (info2->trusted_dc_name == NULL) { + TALLOC_FREE(frame); + return WERR_NOT_ENOUGH_MEMORY; + } + } + info2->tc_connection_status = check_result; + + if (!W_ERROR_IS_OK(info2->pdc_connection_status) || + !W_ERROR_IS_OK(info2->tc_connection_status)) + { + DEBUG(2, ("%s: domain[%s/%s] dcname[%s] " + "pdc_connection[%s] tc_connection[%s]\n", + __func__, domain->name, domain->alt_name, + domain->dcname, + win_errstr(info2->pdc_connection_status), + win_errstr(info2->tc_connection_status))); + } + + r->out.query->info2 = info2; + + DEBUG(5, ("%s: succeeded.\n", __func__)); + TALLOC_FREE(frame); + return WERR_OK; +} + +static WERROR _winbind_LogonControl_CHANGE_PASSWORD(struct pipes_struct *p, + struct winbindd_domain *domain, + struct winbind_LogonControl *r) +{ + struct messaging_context *msg_ctx = global_messaging_context(); + NTSTATUS status; + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + struct cli_credentials *creds = NULL; + struct samr_Password *cur_nt_hash = NULL; + struct netr_NETLOGON_INFO_1 *info1 = NULL; + struct dcerpc_binding_handle *b; + WERROR change_result = WERR_OK; + bool retry = false; + + info1 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_1); + if (info1 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (domain->internal) { + return WERR_NOT_SUPPORTED; + } + + status = pdb_get_trust_credentials(domain->name, + domain->alt_name, + p->mem_ctx, + &creds); + if (NT_STATUS_IS_OK(status)) { + cur_nt_hash = cli_credentials_get_nt_hash(creds, p->mem_ctx); + TALLOC_FREE(creds); + } + +reconnect: + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + reset_cm_connection_on_error(domain, NULL, status); + if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + status = NT_STATUS_NO_LOGON_SERVERS; + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("%s: domain[%s/%s] cm_connect_netlogon() returned %s\n", + __func__, domain->name, domain->alt_name, + nt_errstr(status))); + /* + * Here we return a top level error! + * This is different than TC_QUERY or TC_VERIFY. + */ + return ntstatus_to_werror(status); + } + b = netlogon_pipe->binding_handle; + + if (cur_nt_hash == NULL) { + change_result = WERR_NO_TRUST_LSA_SECRET; + goto change_return; + } + TALLOC_FREE(cur_nt_hash); + + status = trust_pw_change(netlogon_creds_ctx, + msg_ctx, b, domain->name, + domain->dcname, + true); /* force */ + if (!NT_STATUS_IS_OK(status)) { + if (!retry && reset_cm_connection_on_error(domain, b, status)) { + retry = true; + goto reconnect; + } + + DEBUG(1, ("trust_pw_change(%s): %s\n", + domain->name, nt_errstr(status))); + + change_result = ntstatus_to_werror(status); + goto change_return; + } + + change_result = WERR_OK; + +change_return: + info1->pdc_connection_status = change_result; + + if (!W_ERROR_IS_OK(info1->pdc_connection_status)) { + DEBUG(2, ("%s: domain[%s/%s] dcname[%s] " + "pdc_connection[%s]\n", + __func__, domain->name, domain->alt_name, + domain->dcname, + win_errstr(info1->pdc_connection_status))); + } + + r->out.query->info1 = info1; + + DEBUG(5, ("%s: succeeded.\n", __func__)); + return WERR_OK; +} + +WERROR _winbind_LogonControl(struct pipes_struct *p, + struct winbind_LogonControl *r) +{ + struct winbindd_domain *domain; + + domain = wb_child_domain(); + if (domain == NULL) { + return WERR_NO_SUCH_DOMAIN; + } + + switch (r->in.function_code) { + case NETLOGON_CONTROL_REDISCOVER: + if (r->in.level != 2) { + return WERR_INVALID_PARAMETER; + } + return _winbind_LogonControl_REDISCOVER(p, domain, r); + case NETLOGON_CONTROL_TC_QUERY: + if (r->in.level != 2) { + return WERR_INVALID_PARAMETER; + } + return _winbind_LogonControl_TC_QUERY(p, domain, r); + case NETLOGON_CONTROL_TC_VERIFY: + if (r->in.level != 2) { + return WERR_INVALID_PARAMETER; + } + return _winbind_LogonControl_TC_VERIFY(p, domain, r); + case NETLOGON_CONTROL_CHANGE_PASSWORD: + if (r->in.level != 1) { + return WERR_INVALID_PARAMETER; + } + return _winbind_LogonControl_CHANGE_PASSWORD(p, domain, r); + default: + break; + } + + DEBUG(4, ("%s: function_code[0x%x] not supported\n", + __func__, r->in.function_code)); + return WERR_NOT_SUPPORTED; +} + +WERROR _winbind_GetForestTrustInformation(struct pipes_struct *p, + struct winbind_GetForestTrustInformation *r) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status, result; + struct winbindd_domain *domain; + struct rpc_pipe_client *netlogon_pipe = NULL; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + struct dcerpc_binding_handle *b; + bool retry = false; + struct lsa_String trusted_domain_name = {}; + struct lsa_StringLarge trusted_domain_name_l = {}; + union lsa_TrustedDomainInfo *tdi = NULL; + const struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + struct lsa_ForestTrustInformation _old_fti = {}; + struct lsa_ForestTrustInformation *old_fti = NULL; + struct lsa_ForestTrustInformation *new_fti = NULL; + struct lsa_ForestTrustInformation *merged_fti = NULL; + struct lsa_ForestTrustCollisionInfo *collision_info = NULL; + bool update_fti = false; + struct rpc_pipe_client *local_lsa_pipe; + struct policy_handle local_lsa_policy; + struct dcerpc_binding_handle *local_lsa = NULL; + + domain = wb_child_domain(); + if (domain == NULL) { + TALLOC_FREE(frame); + return WERR_NO_SUCH_DOMAIN; + } + + /* + * checking for domain->internal and domain->primary + * makes sure we only do some work when running as DC. + */ + + if (domain->internal) { + TALLOC_FREE(frame); + return WERR_NO_SUCH_DOMAIN; + } + + if (domain->primary) { + TALLOC_FREE(frame); + return WERR_NO_SUCH_DOMAIN; + } + + trusted_domain_name.string = domain->name; + trusted_domain_name_l.string = domain->name; + + status = open_internal_lsa_conn(frame, &local_lsa_pipe, + &local_lsa_policy); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: open_internal_lsa_conn() failed - %s\n", + __location__, __func__, nt_errstr(status))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + local_lsa = local_lsa_pipe->binding_handle; + + status = dcerpc_lsa_QueryTrustedDomainInfoByName(local_lsa, frame, + &local_lsa_policy, + &trusted_domain_name, + LSA_TRUSTED_DOMAIN_INFO_INFO_EX, + &tdi, &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName(%s) failed - %s\n", + __location__, __func__, domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + if (NT_STATUS_EQUAL(result, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + DEBUG(1,("%s:%s: domain[%s] not found via LSA, might be removed already.\n", + __location__, __func__, domain->name)); + TALLOC_FREE(frame); + return WERR_NO_SUCH_DOMAIN; + } + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName(%s) returned %s\n", + __location__, __func__, domain->name, nt_errstr(result))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + if (tdi == NULL) { + DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName() " + "returned no trusted domain information\n", + __location__, __func__)); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + + tdo = &tdi->info_ex; + + if (!(tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) { + DEBUG(2,("%s: tdo[%s/%s] is no forest trust attributes[0x%08X]\n", + __func__, tdo->netbios_name.string, + tdo->domain_name.string, + (unsigned)tdo->trust_attributes)); + TALLOC_FREE(frame); + return WERR_NO_SUCH_DOMAIN; + } + + if (r->in.flags & ~DS_GFTI_UPDATE_TDO) { + TALLOC_FREE(frame); + return WERR_INVALID_FLAGS; + } + +reconnect: + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + reset_cm_connection_on_error(domain, NULL, status); + if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + status = NT_STATUS_NO_LOGON_SERVERS; + } + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("could not open handle to NETLOGON pipe: %s\n", + nt_errstr(status))); + TALLOC_FREE(frame); + return ntstatus_to_werror(status); + } + b = netlogon_pipe->binding_handle; + + status = netlogon_creds_cli_GetForestTrustInformation(netlogon_creds_ctx, + b, p->mem_ctx, + &new_fti); + if (!NT_STATUS_IS_OK(status)) { + if (!retry && reset_cm_connection_on_error(domain, b, status)) { + retry = true; + goto reconnect; + } + DEBUG(2, ("netlogon_creds_cli_GetForestTrustInformation(%s) failed: %s\n", + domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return ntstatus_to_werror(status); + } + + *r->out.forest_trust_info = new_fti; + + if (r->in.flags & DS_GFTI_UPDATE_TDO) { + update_fti = true; + } + + status = dcerpc_lsa_lsaRQueryForestTrustInformation(local_lsa, frame, + &local_lsa_policy, + &trusted_domain_name, + LSA_FOREST_TRUST_DOMAIN_INFO, + &old_fti, &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation(%s) failed %s\n", + __location__, __func__, domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_FOUND)) { + DEBUG(2,("%s: no forest trust information available for domain[%s] yet.\n", + __func__, domain->name)); + update_fti = true; + old_fti = &_old_fti; + result = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation(%s) returned %s\n", + __location__, __func__, domain->name, nt_errstr(result))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + + if (old_fti == NULL) { + DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation() " + "returned success without returning forest trust information\n", + __location__, __func__)); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + + if (!update_fti) { + goto done; + } + + status = dsdb_trust_merge_forest_info(frame, tdo, old_fti, new_fti, + &merged_fti); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: dsdb_trust_merge_forest_info(%s) failed %s\n", + __location__, __func__, domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return ntstatus_to_werror(status); + } + + status = dcerpc_lsa_lsaRSetForestTrustInformation(local_lsa, frame, + &local_lsa_policy, + &trusted_domain_name_l, + LSA_FOREST_TRUST_DOMAIN_INFO, + merged_fti, + 0, /* check_only=0 => store it! */ + &collision_info, + &result); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s:%s: local_lsa.lsaRSetForestTrustInformation(%s) failed %s\n", + __location__, __func__, domain->name, nt_errstr(status))); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("%s:%s: local_lsa.lsaRSetForestTrustInformation(%s) returned %s\n", + __location__, __func__, domain->name, nt_errstr(result))); + TALLOC_FREE(frame); + return ntstatus_to_werror(result); + } + +done: + DEBUG(5, ("_winbind_GetForestTrustInformation succeeded\n")); + TALLOC_FREE(frame); + return WERR_OK; +} + +NTSTATUS _winbind_SendToSam(struct pipes_struct *p, struct winbind_SendToSam *r) +{ + struct winbindd_domain *domain; + NTSTATUS status; + struct rpc_pipe_client *netlogon_pipe; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + struct dcerpc_binding_handle *b = NULL; + bool retry = false; + + DEBUG(5, ("_winbind_SendToSam received\n")); + domain = wb_child_domain(); + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + +reconnect: + status = cm_connect_netlogon_secure(domain, + &netlogon_pipe, + &netlogon_creds_ctx); + reset_cm_connection_on_error(domain, NULL, status); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("could not open handle to NETLOGON pipe\n")); + return status; + } + + b = netlogon_pipe->binding_handle; + + status = netlogon_creds_cli_SendToSam(netlogon_creds_ctx, + b, + &r->in.message); + if (!retry && reset_cm_connection_on_error(domain, b, status)) { + retry = true; + goto reconnect; + } + + return status; +} + +NTSTATUS _wbint_ListTrustedDomains(struct pipes_struct *p, + struct wbint_ListTrustedDomains *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + uint32_t i; + NTSTATUS result; + struct netr_DomainTrustList trusts; + uint32_t count = 0; + struct netr_DomainTrust *array = NULL; + pid_t client_pid; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + /* Cut client_pid to 32bit */ + client_pid = r->in.client_pid; + if ((uint64_t)client_pid != r->in.client_pid) { + DBG_DEBUG("pid out of range\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + DBG_NOTICE("[%s %"PRIu32"]: list trusted domains\n", + r->in.client_name, client_pid); + + result = wb_cache_trusted_domains(domain, p->mem_ctx, &trusts); + if (!NT_STATUS_IS_OK(result)) { + DBG_NOTICE("wb_cache_trusted_domains returned %s\n", + nt_errstr(result)); + return result; + } + + for (i=0; i<trusts.count; i++) { + struct netr_DomainTrust *st = &trusts.array[i]; + struct netr_DomainTrust *dt = NULL; + + if (st->sid == NULL) { + continue; + } + if (dom_sid_equal(st->sid, &global_sid_NULL)) { + continue; + } + + array = talloc_realloc(r->out.domains, array, + struct netr_DomainTrust, + count + 1); + if (array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dt = &array[count]; + + *dt = (struct netr_DomainTrust) { + .trust_flags = st->trust_flags, + .trust_type = st->trust_type, + .trust_attributes = st->trust_attributes, + .netbios_name = talloc_move(array, &st->netbios_name), + .dns_name = talloc_move(array, &st->dns_name), + }; + + dt->sid = dom_sid_dup(array, st->sid); + if (dt->sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + count++; + } + + r->out.domains->array = array; + r->out.domains->count = count; + return NT_STATUS_OK; +} + +#include "librpc/gen_ndr/ndr_winbind_scompat.c" diff --git a/source3/winbindd/winbindd_endgrent.c b/source3/winbindd/winbindd_endgrent.c new file mode 100644 index 0000000..6fdeb8f --- /dev/null +++ b/source3/winbindd/winbindd_endgrent.c @@ -0,0 +1,54 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_ENDGRENT + Copyright (C) Volker Lendecke 2009 + + 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.h" + +struct winbindd_endgrent_state { + uint8_t dummy; +}; + +struct tevent_req *winbindd_endgrent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req; + struct winbindd_endgrent_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_endgrent_state); + if (req == NULL) { + return NULL; + } + D_NOTICE("[%s (%u)] Winbind external command ENDGRENT start.\n", + cli->client_name, + (unsigned int)cli->pid); + + TALLOC_FREE(cli->grent_state); + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +NTSTATUS winbindd_endgrent_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + D_NOTICE("Winbind external command ENDGRENT end.\n"); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_endpwent.c b/source3/winbindd/winbindd_endpwent.c new file mode 100644 index 0000000..a7c14cb --- /dev/null +++ b/source3/winbindd/winbindd_endpwent.c @@ -0,0 +1,55 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_ENDPWENT + Copyright (C) Volker Lendecke 2009 + + 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.h" + +struct winbindd_endpwent_state { + uint8_t dummy; +}; + +struct tevent_req *winbindd_endpwent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req; + struct winbindd_endpwent_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_endpwent_state); + if (req == NULL) { + return NULL; + } + + D_NOTICE("[%s (%u)] Winbind external command ENDPWENT start.\n", + cli->client_name, + (unsigned int)cli->pid); + + TALLOC_FREE(cli->pwent_state); + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +NTSTATUS winbindd_endpwent_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + D_NOTICE("Winbind external command ENDPWENT end.\n"); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getdcname.c b/source3/winbindd/winbindd_getdcname.c new file mode 100644 index 0000000..690e840 --- /dev/null +++ b/source3/winbindd/winbindd_getdcname.c @@ -0,0 +1,95 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETDCNAME + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "lib/util/string_wrappers.h" + +struct winbindd_getdcname_state { + struct netr_DsRGetDCNameInfo *dcinfo; +}; + +static void winbindd_getdcname_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getdcname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getdcname_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getdcname_state); + if (req == NULL) { + return NULL; + } + + request->domain_name[sizeof(request->domain_name)-1] = '\0'; + + D_NOTICE("[%s (%u)] Winbind external command GETDCNAME start.\n" + "Search DCNAME for domain %s.\n", + cli->client_name, + (unsigned int)cli->pid, + request->domain_name); + + subreq = wb_dsgetdcname_send(state, ev, request->domain_name, NULL, + NULL, 0); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getdcname_done, req); + return req; +} + +static void winbindd_getdcname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getdcname_state *state = tevent_req_data( + req, struct winbindd_getdcname_state); + NTSTATUS status; + + status = wb_dsgetdcname_recv(subreq, state, &state->dcinfo); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getdcname_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getdcname_state *state = tevent_req_data( + req, struct winbindd_getdcname_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("getdcname failed: %s\n", nt_errstr(status)); + return status; + } + fstrcpy(response->data.dc_name, strip_hostname(state->dcinfo->dc_unc)); + + D_NOTICE("Winbind external command GETDCNAME end.\n" + "Got DCNAME '%s'.\n", + response->data.dc_name); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getgrent.c b/source3/winbindd/winbindd_getgrent.c new file mode 100644 index 0000000..e8e2b66 --- /dev/null +++ b/source3/winbindd/winbindd_getgrent.c @@ -0,0 +1,213 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETGRENT + Copyright (C) Volker Lendecke 2009 + + 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.h" + +struct winbindd_getgrent_state { + struct tevent_context *ev; + struct winbindd_cli_state *cli; + uint32_t max_groups; + uint32_t num_groups; + struct winbindd_gr *groups; + struct db_context **members; +}; + +static void winbindd_getgrent_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getgrent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getgrent_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getgrent_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->num_groups = 0; + state->cli = cli; + + D_NOTICE("[%s (%u)] Winbind external command GETGRENT start.\n" + "The caller (%s) provided room for %"PRIu32" entries.\n", + cli->client_name, + (unsigned int)cli->pid, + cli->client_name, + request->data.num_entries); + + if (cli->grent_state == NULL) { + D_NOTICE("The grent state from winbindd client state is NULL.\n"); + tevent_req_nterror(req, NT_STATUS_NO_MORE_ENTRIES); + return tevent_req_post(req, ev); + } + + state->max_groups = MIN(500, request->data.num_entries); + if (state->max_groups == 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + state->groups = talloc_zero_array(state, struct winbindd_gr, + state->max_groups); + if (tevent_req_nomem(state->groups, req)) { + return tevent_req_post(req, ev); + } + + state->members = talloc_array(state, struct db_context *, + state->max_groups); + if (tevent_req_nomem(state->members, req)) { + TALLOC_FREE(state->groups); + return tevent_req_post(req, ev); + } + + subreq = wb_next_grent_send(state, ev, lp_winbind_expand_groups(), + cli->grent_state, + &state->groups[state->num_groups]); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getgrent_done, req); + return req; +} + +static void winbindd_getgrent_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getgrent_state *state = tevent_req_data( + req, struct winbindd_getgrent_state); + NTSTATUS status; + + status = wb_next_grent_recv(subreq, state, + &state->members[state->num_groups]); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) { + D_WARNING("winbindd_getgrent_done: done with %"PRIu32" groups\n", + state->num_groups); + TALLOC_FREE(state->cli->grent_state); + tevent_req_done(req); + return; + } + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + state->num_groups += 1; + if (state->num_groups >= state->max_groups) { + D_DEBUG("winbindd_getgrent_done: Got enough groups: %"PRIu32"\n", + state->num_groups); + tevent_req_done(req); + return; + } + if (state->cli->grent_state == NULL) { + D_DEBUG("winbindd_getgrent_done: endgrent called in between\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + subreq = wb_next_grent_send(state, state->ev, + lp_winbind_expand_groups(), + state->cli->grent_state, + &state->groups[state->num_groups]); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_getgrent_done, req); +} + +NTSTATUS winbindd_getgrent_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getgrent_state *state = tevent_req_data( + req, struct winbindd_getgrent_state); + NTSTATUS status; + char **memberstrings; + char *result; + size_t base_memberofs, total_memberlen; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + TALLOC_FREE(state->cli->grent_state); + D_WARNING("getgrent failed: %s\n", nt_errstr(status)); + return status; + } + + if (state->num_groups == 0) { + return NT_STATUS_NO_MORE_ENTRIES; + } + + memberstrings = talloc_array(talloc_tos(), char *, state->num_groups); + if (memberstrings == NULL) { + TALLOC_FREE(state->cli->grent_state); + return NT_STATUS_NO_MEMORY; + } + + total_memberlen = 0; + + for (i=0; i<state->num_groups; i++) { + int num_members; + + status = winbindd_print_groupmembers( + state->members[i], memberstrings, &num_members, + &memberstrings[i]); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(memberstrings); + TALLOC_FREE(state->cli->grent_state); + return status; + } + TALLOC_FREE(state->members[i]); + + state->groups[i].num_gr_mem = num_members; + state->groups[i].gr_mem_ofs = total_memberlen; + + total_memberlen += talloc_get_size(memberstrings[i]); + } + + base_memberofs = state->num_groups * sizeof(struct winbindd_gr); + + result = talloc_realloc(state, state->groups, char, + base_memberofs + total_memberlen); + if (result == NULL) { + TALLOC_FREE(state->cli->grent_state); + return NT_STATUS_NO_MEMORY; + } + state->groups = (struct winbindd_gr *)result; + + for (i=0; i<state->num_groups; i++) { + memcpy(result + base_memberofs + state->groups[i].gr_mem_ofs, + memberstrings[i], talloc_get_size(memberstrings[i])); + TALLOC_FREE(memberstrings[i]); + } + + TALLOC_FREE(memberstrings); + + response->data.num_entries = state->num_groups; + response->length += talloc_get_size(result); + response->extra_data.data = talloc_move(response, &result); + + D_NOTICE("Winbind external command GETGRENT end.\n" + "Received %"PRIu32" entries.\n", + response->data.num_entries); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getgrgid.c b/source3/winbindd/winbindd_getgrgid.c new file mode 100644 index 0000000..4edd81b --- /dev/null +++ b/source3/winbindd/winbindd_getgrgid.c @@ -0,0 +1,156 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETGRGID + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "libcli/security/dom_sid.h" + +struct winbindd_getgrgid_state { + struct tevent_context *ev; + struct unixid xid; + struct dom_sid *sid; + const char *domname; + const char *name; + gid_t gid; + struct db_context *members; +}; + +static void winbindd_getgrgid_gid2sid_done(struct tevent_req *subreq); +static void winbindd_getgrgid_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getgrgid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getgrgid_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getgrgid_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + D_NOTICE("[%s (%u)] Winbind external command GETGRGID start.\n" + "gid=%u\n", + cli->client_name, + (unsigned int)cli->pid, + (int)request->data.gid); + + state->xid = (struct unixid) { + .id = request->data.uid, .type = ID_TYPE_GID }; + + subreq = wb_xids2sids_send(state, ev, &state->xid, 1); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getgrgid_gid2sid_done, + req); + return req; +} + +static void winbindd_getgrgid_gid2sid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getgrgid_state *state = tevent_req_data( + req, struct winbindd_getgrgid_state); + NTSTATUS status; + + status = wb_xids2sids_recv(subreq, state, &state->sid); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (is_null_sid(state->sid)) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP); + return; + } + + subreq = wb_getgrsid_send(state, state->ev, state->sid, + lp_winbind_expand_groups()); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_getgrgid_done, req); +} + +static void winbindd_getgrgid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getgrgid_state *state = tevent_req_data( + req, struct winbindd_getgrgid_state); + NTSTATUS status; + + status = wb_getgrsid_recv(subreq, state, &state->domname, &state->name, + &state->gid, &state->members); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getgrgid_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getgrgid_state *state = tevent_req_data( + req, struct winbindd_getgrgid_state); + NTSTATUS status; + int num_members; + char *buf; + + if (tevent_req_is_nterror(req, &status)) { + struct dom_sid_buf sidbuf; + D_WARNING("Could not convert sid %s: %s\n", + dom_sid_str_buf(state->sid, &sidbuf), + nt_errstr(status)); + return status; + } + + if (!fill_grent(talloc_tos(), &response->data.gr, state->domname, + state->name, state->gid)) { + D_WARNING("fill_grent failed\n"); + return NT_STATUS_NO_MEMORY; + } + + status = winbindd_print_groupmembers(state->members, response, + &num_members, &buf); + if (!NT_STATUS_IS_OK(status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + + response->data.gr.num_gr_mem = (uint32_t)num_members; + + /* Group membership lives at start of extra data */ + + response->data.gr.gr_mem_ofs = 0; + response->extra_data.data = buf; + response->length += talloc_get_size(response->extra_data.data); + + D_NOTICE("Winbind external command GETGRGID end.\n" + "Returning %"PRIu32" group member(s).\n", + response->data.gr.num_gr_mem); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getgrnam.c b/source3/winbindd/winbindd_getgrnam.c new file mode 100644 index 0000000..6b277c2 --- /dev/null +++ b/source3/winbindd/winbindd_getgrnam.c @@ -0,0 +1,213 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETGRNAM + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "libcli/security/dom_sid.h" +#include "lib/util/string_wrappers.h" + +struct winbindd_getgrnam_state { + struct tevent_context *ev; + char *name_namespace; + char *name_domain; + char *name_group; + struct dom_sid sid; + const char *domname; + const char *name; + gid_t gid; + struct db_context *members; +}; + +static void winbindd_getgrnam_lookupname_done(struct tevent_req *subreq); +static void winbindd_getgrnam_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getgrnam_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getgrnam_state *state; + char *tmp; + NTSTATUS nt_status; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getgrnam_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + /* Ensure null termination */ + request->data.groupname[sizeof(request->data.groupname)-1]='\0'; + + D_NOTICE("[%s (%u)] Winbind external command GETGRNAM start.\n" + "Searching group name '%s'.\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.groupname); + + nt_status = normalize_name_unmap(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 = request->data.groupname; + } + + /* Parse domain and groupname */ + + ok = parse_domain_user(state, tmp, + &state->name_namespace, + &state->name_domain, + &state->name_group); + if (!ok) { + DBG_INFO("Could not parse domain user: %s\n", tmp); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + /* if no domain or our local domain and no local tdb group, default to + * our local domain for aliases */ + + if ( !*(state->name_domain) || strequal(state->name_domain, + get_global_sam_name()) ) { + TALLOC_FREE(state->name_domain); + state->name_domain = talloc_strdup(state, + get_global_sam_name()); + if (tevent_req_nomem(state->name_domain, req)) { + return tevent_req_post(req, ev); + } + } + + subreq = wb_lookupname_send(state, ev, + state->name_namespace, + state->name_domain, + state->name_group, + 0); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getgrnam_lookupname_done, + req); + return req; +} + +static void winbindd_getgrnam_lookupname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getgrnam_state *state = tevent_req_data( + req, struct winbindd_getgrnam_state); + enum lsa_SidType type; + NTSTATUS status; + + status = wb_lookupname_recv(subreq, &state->sid, &type); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + switch (type) { + case SID_NAME_DOM_GRP: + case SID_NAME_ALIAS: + case SID_NAME_WKN_GRP: + /* + * Also give user types a chance: + * These might be user sids mapped to the ID_TYPE_BOTH, + * and in that case we should construct a group struct. + */ + case SID_NAME_USER: + case SID_NAME_COMPUTER: + break; + default: + tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP); + return; + } + + subreq = wb_getgrsid_send(state, state->ev, &state->sid, + lp_winbind_expand_groups()); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_getgrnam_done, req); +} + +static void winbindd_getgrnam_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getgrnam_state *state = tevent_req_data( + req, struct winbindd_getgrnam_state); + NTSTATUS status; + + status = wb_getgrsid_recv(subreq, state, &state->domname, &state->name, + &state->gid, &state->members); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getgrnam_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getgrnam_state *state = tevent_req_data( + req, struct winbindd_getgrnam_state); + NTSTATUS status; + int num_members; + char *buf; + + if (tevent_req_is_nterror(req, &status)) { + struct dom_sid_buf sidbuf; + D_WARNING("Could not convert sid %s: %s\n", + dom_sid_str_buf(&state->sid, &sidbuf), + nt_errstr(status)); + return status; + } + + if (!fill_grent(talloc_tos(), &response->data.gr, state->domname, + state->name, state->gid)) { + D_WARNING("fill_grent failed\n"); + return NT_STATUS_NO_MEMORY; + } + + status = winbindd_print_groupmembers(state->members, response, + &num_members, &buf); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + response->data.gr.num_gr_mem = (uint32_t)num_members; + + /* Group membership lives at start of extra data */ + + response->data.gr.gr_mem_ofs = 0; + response->extra_data.data = buf; + response->length += talloc_get_size(response->extra_data.data); + + D_NOTICE("Winbind external command GETGRNAM end.\n" + "Returning %"PRIu32" member(s).\n", + response->data.gr.num_gr_mem); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getgroups.c b/source3/winbindd/winbindd_getgroups.c new file mode 100644 index 0000000..c1c108e --- /dev/null +++ b/source3/winbindd/winbindd_getgroups.c @@ -0,0 +1,283 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETGROUPS + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "passdb/lookup_sid.h" /* only for LOOKUP_NAME_NO_NSS flag */ +#include "libcli/security/dom_sid.h" + +struct winbindd_getgroups_state { + struct tevent_context *ev; + char *namespace; + char *domname; + char *username; + struct dom_sid sid; + enum lsa_SidType type; + uint32_t num_sids; + struct dom_sid *sids; + uint32_t num_gids; + gid_t *gids; +}; + +static void winbindd_getgroups_lookupname_done(struct tevent_req *subreq); +static void winbindd_getgroups_gettoken_done(struct tevent_req *subreq); +static void winbindd_getgroups_sid2gid_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getgroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getgroups_state *state; + char *domuser, *mapped_user; + NTSTATUS status; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getgroups_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + /* Ensure null termination */ + request->data.username[sizeof(request->data.username)-1]='\0'; + + D_NOTICE("[%s (%u)] Winbind external command GETGROUPS start.\n" + "Searching groups for username '%s'.\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.username); + + domuser = request->data.username; + + status = normalize_name_unmap(state, domuser, &mapped_user); + + if (NT_STATUS_IS_OK(status) + || NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) { + /* normalize_name_unmapped did something */ + domuser = mapped_user; + } + + ok = parse_domain_user(state, domuser, + &state->namespace, + &state->domname, + &state->username); + if (!ok) { + D_WARNING("Could not parse domain user: %s\n", domuser); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = wb_lookupname_send(state, ev, + state->namespace, + state->domname, + state->username, + LOOKUP_NAME_NO_NSS); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getgroups_lookupname_done, + req); + return req; +} + +static void winbindd_getgroups_lookupname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getgroups_state *state = tevent_req_data( + req, struct winbindd_getgroups_state); + NTSTATUS status; + + status = wb_lookupname_recv(subreq, &state->sid, &state->type); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = wb_gettoken_send(state, state->ev, &state->sid, true); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_getgroups_gettoken_done, req); +} + +static void winbindd_getgroups_gettoken_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getgroups_state *state = tevent_req_data( + req, struct winbindd_getgroups_state); + NTSTATUS status; + + status = wb_gettoken_recv(subreq, state, &state->num_sids, + &state->sids); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + /* + * Convert the group SIDs to gids. state->sids[0] contains the user + * sid. If the idmap backend uses ID_TYPE_BOTH, we might need the + * the id of the user sid in the list of group sids, so map the + * complete token. + */ + + subreq = wb_sids2xids_send(state, state->ev, + state->sids, state->num_sids); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_getgroups_sid2gid_done, req); +} + +static void winbindd_getgroups_sid2gid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getgroups_state *state = tevent_req_data( + req, struct winbindd_getgroups_state); + NTSTATUS status; + struct unixid *xids; + uint32_t i; + + xids = talloc_array(state, struct unixid, state->num_sids); + if (tevent_req_nomem(xids, req)) { + return; + } + for (i=0; i < state->num_sids; i++) { + xids[i].type = ID_TYPE_NOT_SPECIFIED; + xids[i].id = UINT32_MAX; + } + + status = wb_sids2xids_recv(subreq, xids, state->num_sids); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED) || + NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) + { + status = NT_STATUS_OK; + } + if (tevent_req_nterror(req, status)) { + return; + } + + state->gids = talloc_array(state, gid_t, state->num_sids); + if (tevent_req_nomem(state->gids, req)) { + return; + } + state->num_gids = 0; + + for (i=0; i < state->num_sids; i++) { + bool include_gid = false; + const char *debug_missing = NULL; + + switch (xids[i].type) { + case ID_TYPE_NOT_SPECIFIED: + debug_missing = "not specified"; + break; + case ID_TYPE_UID: + if (i != 0) { + debug_missing = "uid"; + } + break; + case ID_TYPE_GID: + case ID_TYPE_BOTH: + include_gid = true; + break; + case ID_TYPE_WB_REQUIRE_TYPE: + /* + * these are internal between winbindd + * parent and child. + */ + smb_panic(__location__); + break; + } + + if (!include_gid) { + struct dom_sid_buf sidbuf; + + if (debug_missing == NULL) { + continue; + } + + D_WARNING("WARNING: skipping unix id (%"PRIu32") for sid %s " + "from group list because the idmap type " + "is %s. " + "This might be a security problem when ACLs " + "contain DENY ACEs!\n", + (unsigned)xids[i].id, + dom_sid_str_buf(&state->sids[i], &sidbuf), + debug_missing); + continue; + } + + state->gids[state->num_gids] = (gid_t)xids[i].id; + state->num_gids += 1; + } + + /* + * This should not fail, as it does not do any reallocation, + * just updating the talloc size. + */ + state->gids = talloc_realloc(state, state->gids, gid_t, state->num_gids); + if (tevent_req_nomem(state->gids, req)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS winbindd_getgroups_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getgroups_state *state = tevent_req_data( + req, struct winbindd_getgroups_state); + NTSTATUS status; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + struct dom_sid_buf buf; + D_WARNING("Could not convert sid %s: %s\n", + dom_sid_str_buf(&state->sid, &buf), + nt_errstr(status)); + return status; + } + + response->data.num_entries = state->num_gids; + + D_NOTICE("Winbind external command GETGROUPS end.\n" + "Received %"PRIu32" entries.\n", + response->data.num_entries); + if (CHECK_DEBUGLVL(DBGLVL_NOTICE)) { + for (i = 0; i < state->num_gids; i++) { + D_NOTICE("%"PRIu32": GID %u\n", i, state->gids[i]); + } + } + + if (state->num_gids > 0) { + response->extra_data.data = talloc_move(response, + &state->gids); + response->length += state->num_gids * sizeof(gid_t); + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getpwent.c b/source3/winbindd/winbindd_getpwent.c new file mode 100644 index 0000000..5a78883 --- /dev/null +++ b/source3/winbindd/winbindd_getpwent.c @@ -0,0 +1,162 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETPWENT + Copyright (C) Volker Lendecke 2009 + + 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.h" + +struct winbindd_getpwent_state { + struct tevent_context *ev; + struct winbindd_cli_state *cli; + uint32_t max_users; + uint32_t num_users; + struct winbindd_pw *users; +}; + +static void winbindd_getpwent_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getpwent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getpwent_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getpwent_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->num_users = 0; + state->cli = cli; + + D_NOTICE("[%s (%u)] Winbind external command GETPWENT start.\n" + "The caller (%s) provided room for %d entries.\n", + cli->client_name, + (unsigned int)cli->pid, + cli->client_name, + request->data.num_entries); + + if (cli->pwent_state == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_MORE_ENTRIES); + return tevent_req_post(req, ev); + } + + state->max_users = MIN(500, request->data.num_entries); + if (state->max_users == 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + state->users = talloc_zero_array(state, struct winbindd_pw, + state->max_users); + if (tevent_req_nomem(state->users, req)) { + return tevent_req_post(req, ev); + } + + subreq = wb_next_pwent_send(state, ev, cli->pwent_state, + &state->users[state->num_users]); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getpwent_done, req); + return req; +} + +static void winbindd_getpwent_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getpwent_state *state = tevent_req_data( + req, struct winbindd_getpwent_state); + NTSTATUS status; + + status = wb_next_pwent_recv(subreq); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) { + D_DEBUG("winbindd_getpwent_done: done with %"PRIu32" users\n", + state->num_users); + TALLOC_FREE(state->cli->pwent_state); + tevent_req_done(req); + return; + } + if (tevent_req_nterror(req, status)) { + return; + } + state->num_users += 1; + if (state->num_users >= state->max_users) { + D_DEBUG("winbindd_getpwent_done: Got enough users: %"PRIu32"\n", + state->num_users); + tevent_req_done(req); + return; + } + if (state->cli->pwent_state == NULL) { + D_DEBUG("winbindd_getpwent_done: endpwent called in between\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return; + } + subreq = wb_next_pwent_send(state, state->ev, state->cli->pwent_state, + &state->users[state->num_users]); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_getpwent_done, req); +} + +NTSTATUS winbindd_getpwent_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getpwent_state *state = tevent_req_data( + req, struct winbindd_getpwent_state); + NTSTATUS status; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + TALLOC_FREE(state->cli->pwent_state); + D_WARNING("getpwent failed: %s\n", nt_errstr(status)); + return status; + } + + D_NOTICE("Winbind external command GETPWENT end.\n" + "Received %"PRIu32" entries.\n" + "(name:passwd:uid:gid:gecos:dir:shell)\n", + state->num_users); + + if (state->num_users == 0) { + return NT_STATUS_NO_MORE_ENTRIES; + } + + for (i = 0; i < state->num_users; i++) { + D_NOTICE("%"PRIu32": %s:%s:%u:%u:%s:%s:%s\n", + i, + state->users[i].pw_name, + state->users[i].pw_passwd, + (unsigned int)state->users[i].pw_uid, + (unsigned int)state->users[i].pw_gid, + state->users[i].pw_gecos, + state->users[i].pw_dir, + state->users[i].pw_shell + ); + } + response->data.num_entries = state->num_users; + response->extra_data.data = talloc_move(response, &state->users); + response->length += state->num_users * sizeof(struct winbindd_pw); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getpwnam.c b/source3/winbindd/winbindd_getpwnam.c new file mode 100644 index 0000000..2bf15c0 --- /dev/null +++ b/source3/winbindd/winbindd_getpwnam.c @@ -0,0 +1,163 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETPWNAM + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "passdb/lookup_sid.h" /* only for LOOKUP_NAME_NO_NSS flag */ +#include "libcli/security/dom_sid.h" + +struct winbindd_getpwnam_state { + struct tevent_context *ev; + char *namespace; + char *domname; + char *username; + struct dom_sid sid; + enum lsa_SidType type; + struct winbindd_pw pw; +}; + +static void winbindd_getpwnam_lookupname_done(struct tevent_req *subreq); +static void winbindd_getpwnam_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getpwnam_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getpwnam_state *state; + char *domuser, *mapped_user; + NTSTATUS status; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getpwnam_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + /* Ensure null termination */ + request->data.username[sizeof(request->data.username)-1]='\0'; + + D_NOTICE("[%s (%u)] Winbind external command GETPWNAM start.\n" + "Query username '%s'.\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.username); + + domuser = request->data.username; + + status = normalize_name_unmap(state, domuser, &mapped_user); + + if (NT_STATUS_IS_OK(status) + || NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) { + /* normalize_name_unmapped did something */ + domuser = mapped_user; + } + + ok = parse_domain_user(state, + domuser, + &state->namespace, + &state->domname, + &state->username); + if (!ok) { + D_WARNING("Could not parse domain user: %s\n", domuser); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = wb_lookupname_send(state, ev, + state->namespace, + state->domname, + state->username, + LOOKUP_NAME_NO_NSS); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getpwnam_lookupname_done, + req); + return req; +} + +static void winbindd_getpwnam_lookupname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getpwnam_state *state = tevent_req_data( + req, struct winbindd_getpwnam_state); + NTSTATUS status; + + status = wb_lookupname_recv(subreq, &state->sid, &state->type); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + subreq = wb_getpwsid_send(state, state->ev, &state->sid, &state->pw); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_getpwnam_done, req); +} + +static void winbindd_getpwnam_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + NTSTATUS status; + + status = wb_getpwsid_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getpwnam_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getpwnam_state *state = tevent_req_data( + req, struct winbindd_getpwnam_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + struct dom_sid_buf buf; + D_WARNING("Could not convert sid %s: %s\n", + dom_sid_str_buf(&state->sid, &buf), + nt_errstr(status)); + return status; + } + response->data.pw = state->pw; + + D_NOTICE("Winbind external command GETPWNAM end.\n" + "(name:passwd:uid:gid:gecos:dir:shell)\n" + "%s:%s:%u:%u:%s:%s:%s\n", + state->pw.pw_name, + state->pw.pw_passwd, + (unsigned int)state->pw.pw_uid, + (unsigned int)state->pw.pw_gid, + state->pw.pw_gecos, + state->pw.pw_dir, + state->pw.pw_shell + ); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getpwsid.c b/source3/winbindd/winbindd_getpwsid.c new file mode 100644 index 0000000..8e0a3e3 --- /dev/null +++ b/source3/winbindd/winbindd_getpwsid.c @@ -0,0 +1,109 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETPWSID + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "../libcli/security/security.h" + +struct winbindd_getpwsid_state { + struct dom_sid sid; + struct winbindd_pw pw; +}; + +static void winbindd_getpwsid_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getpwsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getpwsid_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getpwsid_state); + if (req == NULL) { + return NULL; + } + + /* Ensure null termination */ + request->data.sid[sizeof(request->data.sid)-1]='\0'; + + D_NOTICE("[%s (%u)] Winbind external command GETPWSID start.\n" + "sid=%s\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.sid); + + if (!string_to_sid(&state->sid, request->data.sid)) { + D_WARNING("Could not get convert sid %s from string\n", + request->data.sid); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = wb_getpwsid_send(state, ev, &state->sid, &state->pw); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getpwsid_done, req); + return req; +} + +static void winbindd_getpwsid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + NTSTATUS status; + + status = wb_getpwsid_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getpwsid_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getpwsid_state *state = tevent_req_data( + req, struct winbindd_getpwsid_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + response->data.pw = state->pw; + + D_NOTICE("Winbind external command GETPWSID end.\n" + "(name:passwd:uid:gid:gecos:dir:shell)\n" + "%s:%s:%u:%u:%s:%s:%s\n", + state->pw.pw_name, + state->pw.pw_passwd, + (unsigned int)state->pw.pw_uid, + (unsigned int)state->pw.pw_gid, + state->pw.pw_gecos, + state->pw.pw_dir, + state->pw.pw_shell + ); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getpwuid.c b/source3/winbindd/winbindd_getpwuid.c new file mode 100644 index 0000000..509e013 --- /dev/null +++ b/source3/winbindd/winbindd_getpwuid.c @@ -0,0 +1,137 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETPWUID + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "libcli/security/dom_sid.h" + +struct winbindd_getpwuid_state { + struct tevent_context *ev; + struct unixid xid; + struct dom_sid *sid; + struct winbindd_pw pw; +}; + +static void winbindd_getpwuid_uid2sid_done(struct tevent_req *subreq); +static void winbindd_getpwuid_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getpwuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getpwuid_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getpwuid_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + D_NOTICE("[%s (%u)] Winbind external command GETPWUID start.\n" + "Search UID %u.\n", + cli->client_name, + (unsigned int)cli->pid, + (int)request->data.uid); + + state->xid = (struct unixid) { + .id = request->data.uid, .type = ID_TYPE_UID }; + + subreq = wb_xids2sids_send(state, ev, &state->xid, 1); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getpwuid_uid2sid_done, + req); + return req; +} + +static void winbindd_getpwuid_uid2sid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getpwuid_state *state = tevent_req_data( + req, struct winbindd_getpwuid_state); + NTSTATUS status; + + status = wb_xids2sids_recv(subreq, state, &state->sid); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return; + } + if (is_null_sid(state->sid)) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + D_WARNING("Failed with NT_STATUS_NO_SUCH_USER.\n"); + return; + } + + subreq = wb_getpwsid_send(state, state->ev, state->sid, &state->pw); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_getpwuid_done, req); +} + +static void winbindd_getpwuid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + NTSTATUS status; + + status = wb_getpwsid_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getpwuid_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getpwuid_state *state = tevent_req_data( + req, struct winbindd_getpwuid_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + struct dom_sid_buf buf; + D_WARNING("Could not convert sid %s: %s\n", + dom_sid_str_buf(state->sid, &buf), + nt_errstr(status)); + return status; + } + response->data.pw = state->pw; + D_NOTICE("Winbind external command GETPWUID end.\n" + "(name:passwd:uid:gid:gecos:dir:shell)\n" + "%s:%s:%u:%u:%s:%s:%s\n", + state->pw.pw_name, + state->pw.pw_passwd, + (unsigned int)state->pw.pw_uid, + (unsigned int)state->pw.pw_gid, + state->pw.pw_gecos, + state->pw.pw_dir, + state->pw.pw_shell + ); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getsidaliases.c b/source3/winbindd/winbindd_getsidaliases.c new file mode 100644 index 0000000..bf8fcdd --- /dev/null +++ b/source3/winbindd/winbindd_getsidaliases.c @@ -0,0 +1,160 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETSIDALIASES + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "../libcli/security/security.h" + +struct winbindd_getsidaliases_state { + struct dom_sid sid; + uint32_t num_aliases; + uint32_t *aliases; +}; + +static void winbindd_getsidaliases_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getsidaliases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getsidaliases_state *state; + struct winbindd_domain *domain; + uint32_t num_sids, i; + struct dom_sid *sids; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getsidaliases_state); + if (req == NULL) { + return NULL; + } + + /* Ensure null termination */ + request->data.sid[sizeof(request->data.sid)-1]='\0'; + + if (!string_to_sid(&state->sid, request->data.sid)) { + D_WARNING("Could not get convert sid %s from string\n", + request->data.sid); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + domain = find_domain_from_sid_noinit(&state->sid); + if (domain == NULL) { + D_WARNING("could not find domain entry for sid %s\n", + request->data.sid); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + + num_sids = 0; + sids = NULL; + + if (request->extra_data.data != NULL) { + if (request->extra_data.data[request->extra_len-1] != '\0') { + D_WARNING("Got non-NULL terminated sidlist\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + if (!parse_sidlist(state, request->extra_data.data, + &sids, &num_sids)) { + D_WARNING("Could not parse SID list: %s\n", + request->extra_data.data); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + } + + D_NOTICE("[%s (%u)] Winbind external command GETSIDALIASES start.\n" + "sid=%s\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.sid); + if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) { + for (i = 0; i < num_sids; i++) { + struct dom_sid_buf sidstr; + D_NOTICE("%"PRIu32": %s\n", + i, dom_sid_str_buf(&sids[i], &sidstr)); + } + } + + subreq = wb_lookupuseraliases_send(state, ev, domain, num_sids, sids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getsidaliases_done, req); + return req; +} + +static void winbindd_getsidaliases_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getsidaliases_state *state = tevent_req_data( + req, struct winbindd_getsidaliases_state); + NTSTATUS status; + + status = wb_lookupuseraliases_recv(subreq, state, &state->num_aliases, + &state->aliases); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getsidaliases_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getsidaliases_state *state = tevent_req_data( + req, struct winbindd_getsidaliases_state); + NTSTATUS status; + uint32_t i; + char *sidlist; + + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + + sidlist = talloc_strdup(response, ""); + + D_NOTICE("Winbind external command GETSIDALIASES end.\n" + "Received %"PRIu32" alias(es).\n", + state->num_aliases); + for (i=0; i<state->num_aliases; i++) { + struct dom_sid sid; + struct dom_sid_buf tmp; + sid_compose(&sid, &state->sid, state->aliases[i]); + + talloc_asprintf_addbuf( + &sidlist, "%s\n", dom_sid_str_buf(&sid, &tmp)); + D_NOTICE("%"PRIu32": %s\n", i, dom_sid_str_buf(&sid, &tmp)); + } + + if (sidlist == NULL) { + return NT_STATUS_NO_MEMORY; + } + + response->extra_data.data = sidlist; + response->length += talloc_get_size(sidlist); + response->data.num_entries = state->num_aliases; + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getuserdomgroups.c b/source3/winbindd/winbindd_getuserdomgroups.c new file mode 100644 index 0000000..75eb437 --- /dev/null +++ b/source3/winbindd/winbindd_getuserdomgroups.c @@ -0,0 +1,123 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETUSERDOMGROUPS + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "../libcli/security/security.h" + +struct winbindd_getuserdomgroups_state { + struct dom_sid sid; + uint32_t num_sids; + struct dom_sid *sids; +}; + +static void winbindd_getuserdomgroups_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getuserdomgroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getuserdomgroups_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getuserdomgroups_state); + if (req == NULL) { + return NULL; + } + + /* Ensure null termination */ + request->data.sid[sizeof(request->data.sid)-1]='\0'; + + D_NOTICE("[%s (%u)] Winbind external command GETUSERDOMGROUPS start.\n" + "sid=%s\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.sid); + + if (!string_to_sid(&state->sid, request->data.sid)) { + D_WARNING("Could not get convert sid %s from string\n", + request->data.sid); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = wb_gettoken_send(state, ev, &state->sid, false); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getuserdomgroups_done, req); + return req; +} + +static void winbindd_getuserdomgroups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getuserdomgroups_state *state = tevent_req_data( + req, struct winbindd_getuserdomgroups_state); + NTSTATUS status; + + status = wb_gettoken_recv(subreq, state, &state->num_sids, + &state->sids); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getuserdomgroups_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getuserdomgroups_state *state = tevent_req_data( + req, struct winbindd_getuserdomgroups_state); + NTSTATUS status; + uint32_t i; + char *sidlist; + + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + + sidlist = talloc_strdup(response, ""); + if (sidlist == NULL) { + return NT_STATUS_NO_MEMORY; + } + D_NOTICE("Winbind external command GETUSERDOMGROUPS end.\n" + "Received %"PRIu32" entries.\n", + state->num_sids); + for (i=0; i<state->num_sids; i++) { + struct dom_sid_buf tmp; + sidlist = talloc_asprintf_append_buffer( + sidlist, "%s\n", + dom_sid_str_buf(&state->sids[i], &tmp)); + if (sidlist == NULL) { + return NT_STATUS_NO_MEMORY; + } + D_NOTICE("%"PRIu32": %s\n", + i, dom_sid_str_buf(&state->sids[i], &tmp)); + } + response->extra_data.data = sidlist; + response->length += talloc_get_size(sidlist); + response->data.num_entries = state->num_sids; + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_getusersids.c b/source3/winbindd/winbindd_getusersids.c new file mode 100644 index 0000000..a285c1f --- /dev/null +++ b/source3/winbindd/winbindd_getusersids.c @@ -0,0 +1,128 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_GETUSERSIDS + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "../libcli/security/security.h" + +struct winbindd_getusersids_state { + struct dom_sid sid; + uint32_t num_sids; + struct dom_sid *sids; +}; + +static void winbindd_getusersids_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_getusersids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_getusersids_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_getusersids_state); + if (req == NULL) { + return NULL; + } + + /* Ensure null termination */ + request->data.sid[sizeof(request->data.sid)-1]='\0'; + + D_NOTICE("[%s (%u)] Winbind external command GETUSERSIDS start.\n" + "sid=%s\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.sid); + + if (!string_to_sid(&state->sid, request->data.sid)) { + D_WARNING("Returning NT_STATUS_INVALID_PARAMETER.\n" + "Could not get convert sid %s from string\n", + request->data.sid); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = wb_gettoken_send(state, ev, &state->sid, true); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_getusersids_done, req); + return req; +} + +static void winbindd_getusersids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_getusersids_state *state = tevent_req_data( + req, struct winbindd_getusersids_state); + NTSTATUS status; + + status = wb_gettoken_recv(subreq, state, &state->num_sids, + &state->sids); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + D_WARNING("wb_gettoken_recv failed with %s.\n", + nt_errstr(status)); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_getusersids_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_getusersids_state *state = tevent_req_data( + req, struct winbindd_getusersids_state); + struct dom_sid_buf sidbuf; + NTSTATUS status; + uint32_t i; + char *result; + + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Could not convert sid %s: %s\n", + dom_sid_str_buf(&state->sid, &sidbuf), + nt_errstr(status)); + return status; + } + + result = talloc_strdup(response, ""); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + + D_NOTICE("Winbind external command GETUSERSIDS end.\n" + "Got %"PRIu32" SID(s).\n", state->num_sids); + for (i=0; i<state->num_sids; i++) { + D_NOTICE("%"PRIu32": %s\n", + i, + dom_sid_str_buf(&state->sids[i], &sidbuf)); + talloc_asprintf_addbuf( + &result, + "%s\n", + dom_sid_str_buf(&state->sids[i], &sidbuf)); + } + + response->data.num_entries = state->num_sids; + response->extra_data.data = result; + response->length += talloc_get_size(result); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_gpupdate.c b/source3/winbindd/winbindd_gpupdate.c new file mode 100644 index 0000000..1ab20fb --- /dev/null +++ b/source3/winbindd/winbindd_gpupdate.c @@ -0,0 +1,184 @@ +/* + * Unix SMB/CIFS implementation. + * Group Policy Update event for winbindd + * Copyright (C) David Mulder 2017 + * + * 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 "param/param.h" +#include "param/loadparm.h" +#include "winbindd.h" +#include "lib/global_contexts.h" + +/* + * gpupdate_interval() + * return Random integer between 5400 and 7200, the group policy update + * interval in seconds + * + * Group Policy should be updated every 90 minutes in the background, + * with a random offset between 0 and 30 minutes. This ensures multiple + * clients will not update at the same time. + */ +#define GPUPDATE_INTERVAL (90*60) +#define GPUPDATE_RAND_OFFSET (30*60) +static uint32_t gpupdate_interval(void) +{ + int rand_int_offset = generate_random() % GPUPDATE_RAND_OFFSET; + return GPUPDATE_INTERVAL+rand_int_offset; +} + +struct gpupdate_state { + TALLOC_CTX *ctx; + struct loadparm_context *lp_ctx; +}; + +static void gpupdate_cmd_done(struct tevent_req *subreq); + +static void gpupdate_callback(struct tevent_context *ev, + struct tevent_timer *tim, + struct timeval current_time, + void *private_data) +{ + struct tevent_timer *time_event; + struct timeval schedule; + struct tevent_req *req = NULL; + struct gpupdate_state *data = + talloc_get_type_abort(private_data, struct gpupdate_state); + const char *const *gpupdate_cmd = + lpcfg_gpo_update_command(data->lp_ctx); + const char *smbconf = lpcfg_configfile(data->lp_ctx); + if (smbconf == NULL) { + smbconf = lp_default_path(); + } + + /* Execute gpupdate */ + req = samba_runcmd_send(data->ctx, ev, timeval_zero(), 2, 0, + gpupdate_cmd, + "-s", + smbconf, + "--target=Computer", + "--machine-pass", + NULL); + if (req == NULL) { + DEBUG(0, ("Failed to execute the gpupdate command\n")); + return; + } + + tevent_req_set_callback(req, gpupdate_cmd_done, NULL); + + /* Schedule the next event */ + schedule = tevent_timeval_current_ofs(gpupdate_interval(), 0); + time_event = tevent_add_timer(ev, data->ctx, schedule, + gpupdate_callback, data); + if (time_event == NULL) { + DEBUG(0, ("Failed scheduling the next gpupdate event\n")); + } +} + +void gpupdate_init(void) +{ + struct tevent_timer *time_event; + struct timeval schedule; + TALLOC_CTX * ctx = talloc_new(global_event_context()); + struct gpupdate_state *data = talloc(ctx, struct gpupdate_state); + struct loadparm_context *lp_ctx = + loadparm_init_s3(NULL, loadparm_s3_helpers()); + + /* + * Check if gpupdate is enabled for winbind, if not + * return without scheduling any events. + */ + if (!lpcfg_apply_group_policies(lp_ctx)) { + return; + } + + /* + * Execute the first event immediately, future events + * will execute on the gpupdate interval, which is every + * 90 to 120 minutes (at random). + */ + schedule = tevent_timeval_current_ofs(0, 0); + data->ctx = ctx; + data->lp_ctx = lp_ctx; + if (data->lp_ctx == NULL) { + smb_panic("Could not load smb.conf\n"); + } + time_event = tevent_add_timer(global_event_context(), data->ctx, + schedule, gpupdate_callback, data); + if (time_event == NULL) { + DEBUG(0, ("Failed scheduling the gpupdate event\n")); + } +} + +void gpupdate_user_init(const char *user) +{ + struct tevent_req *req = NULL; + TALLOC_CTX *ctx = talloc_new(global_event_context()); + struct loadparm_context *lp_ctx = + loadparm_init_s3(NULL, loadparm_s3_helpers()); + const char *const *gpupdate_cmd = lpcfg_gpo_update_command(lp_ctx); + const char *smbconf = lpcfg_configfile(lp_ctx); + if (smbconf == NULL) { + smbconf = lp_default_path(); + } + + if (ctx == NULL) { + DBG_ERR("talloc_new failed\n"); + return; + } + + /* + * Check if gpupdate is enabled for winbind, if not + * return without applying user policy. + */ + if (!lpcfg_apply_group_policies(lp_ctx)) { + return; + } + + /* + * Execute gpupdate for the user immediately. + * TODO: This should be scheduled to reapply every 90 to 120 minutes. + * Logoff will need to handle cancelling these events though, and + * multiple timers cannot be run for the same user, even if there are + * multiple active sessions. + */ + req = samba_runcmd_send(ctx, global_event_context(), + timeval_zero(), 2, 0, + gpupdate_cmd, + "-s", + smbconf, + "--target=User", + "-U", + user, + NULL); + if (req == NULL) { + DBG_ERR("Failed to execute the gpupdate command\n"); + return; + } + + tevent_req_set_callback(req, gpupdate_cmd_done, NULL); +} + +static void gpupdate_cmd_done(struct tevent_req *subreq) +{ + int sys_errno; + int ret; + + ret = samba_runcmd_recv(subreq, &sys_errno); + TALLOC_FREE(subreq); + if (ret != 0) { + DBG_ERR("gpupdate failed with exit status %d\n", sys_errno); + } +} diff --git a/source3/winbindd/winbindd_group.c b/source3/winbindd/winbindd_group.c new file mode 100644 index 0000000..b233c8e --- /dev/null +++ b/source3/winbindd/winbindd_group.c @@ -0,0 +1,156 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) Tim Potter 2000 + Copyright (C) Jeremy Allison 2001. + Copyright (C) Gerald (Jerry) Carter 2003. + Copyright (C) Volker Lendecke 2005 + + 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.h" +#include "lib/dbwrap/dbwrap.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* Fill a grent structure from various other information */ + +bool fill_grent(TALLOC_CTX *mem_ctx, struct winbindd_gr *gr, + const char *dom_name, const char *gr_name, gid_t unix_gid) +{ + const char *full_group_name; + char *mapped_name = NULL; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + + nt_status = normalize_name_map(mem_ctx, dom_name, gr_name, + &mapped_name); + + D_DEBUG("Filling domain '%s' and group '%s'.\n", dom_name, gr_name); + /* Basic whitespace replacement */ + if (NT_STATUS_IS_OK(nt_status)) { + full_group_name = fill_domain_username_talloc(mem_ctx, dom_name, + mapped_name, true); + } + /* Mapped to an alias */ + else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) { + full_group_name = mapped_name; + } + /* no change */ + else { + full_group_name = fill_domain_username_talloc(mem_ctx, dom_name, + gr_name, True ); + } + + if (full_group_name == NULL) { + D_DEBUG("Returning false, since there is no full group name.\n"); + return false; + } + + gr->gr_gid = unix_gid; + + /* Group name and password */ + + strlcpy(gr->gr_name, full_group_name, sizeof(gr->gr_name)); + strlcpy(gr->gr_passwd, "x", sizeof(gr->gr_passwd)); + + D_DEBUG("Returning true. Full group name is '%s'.\n", gr_name); + return True; +} + +struct getgr_countmem { + int num; + size_t len; +}; + +static int getgr_calc_memberlen(struct db_record *rec, void *private_data) +{ + struct getgr_countmem *buf = private_data; + TDB_DATA data = dbwrap_record_get_value(rec); + size_t len; + + buf->num += 1; + + len = buf->len + data.dsize; + if (len < buf->len) { + return 0; + } + buf->len = len; + return 0; +} + +struct getgr_stringmem { + size_t ofs; + char *buf; +}; + +static int getgr_unparse_members(struct db_record *rec, void *private_data) +{ + struct getgr_stringmem *buf = private_data; + TDB_DATA data = dbwrap_record_get_value(rec); + int len; + + len = data.dsize-1; + + memcpy(buf->buf + buf->ofs, data.dptr, len); + buf->ofs += len; + buf->buf[buf->ofs] = ','; + buf->ofs += 1; + return 0; +} + +NTSTATUS winbindd_print_groupmembers(struct db_context *members, + TALLOC_CTX *mem_ctx, + int *num_members, char **result) +{ + struct getgr_countmem c; + struct getgr_stringmem m; + int count; + NTSTATUS status; + + c.num = 0; + c.len = 0; + + status = dbwrap_traverse(members, getgr_calc_memberlen, &c, &count); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("dbwrap_traverse failed: %s\n", nt_errstr(status)); + return status; + } + + m.ofs = 0; + m.buf = talloc_array(mem_ctx, char, c.len); + if (m.buf == NULL) { + D_WARNING("talloc failed\n"); + return NT_STATUS_NO_MEMORY; + } + + status = dbwrap_traverse(members, getgr_unparse_members, &m, &count); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(m.buf); + DBG_NOTICE("dbwrap_traverse failed: %s\n", nt_errstr(status)); + return status; + } + if (c.len > 0) { + m.buf[c.len - 1] = '\0'; + } + + *num_members = c.num; + *result = m.buf; + D_DEBUG("Returning %d member(s).\n", *num_members); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_idmap.c b/source3/winbindd/winbindd_idmap.c new file mode 100644 index 0000000..3622112 --- /dev/null +++ b/source3/winbindd/winbindd_idmap.c @@ -0,0 +1,436 @@ +/* + Unix SMB/CIFS implementation. + + Async helpers for blocking functions + + Copyright (C) Volker Lendecke 2005 + Copyright (C) Gerald Carter 2006 + Copyright (C) Simo Sorce 2007 + + 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.h" +#include "../libcli/security/security.h" +#include "passdb/lookup_sid.h" +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +static struct winbindd_child *static_idmap_child = NULL; + +/* + * Map idmap ranges to domain names, taken from smb.conf. This is + * stored in the parent winbind and used to assemble xids2sids/sids2xids calls + * into per-idmap-domain chunks. + */ +static struct wb_parent_idmap_config static_parent_idmap_config; + +struct winbindd_child *idmap_child(void) +{ + return static_idmap_child; +} + +bool is_idmap_child(const struct winbindd_child *child) +{ + if (child == static_idmap_child) { + return true; + } + + return false; +} + +pid_t idmap_child_pid(void) +{ + return static_idmap_child->pid; +} + +struct dcerpc_binding_handle *idmap_child_handle(void) +{ + /* + * The caller needs to use wb_parent_idmap_setup_send/recv + * before talking to the idmap child! + */ + SMB_ASSERT(static_parent_idmap_config.num_doms > 0); + return static_idmap_child->binding_handle; +} + +static void init_idmap_child_done(struct tevent_req *subreq); + +NTSTATUS init_idmap_child(TALLOC_CTX *mem_ctx) +{ + struct tevent_req *subreq = NULL; + + if (static_idmap_child != NULL) { + DBG_ERR("idmap child already allocated\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + static_idmap_child = talloc_zero(mem_ctx, struct winbindd_child); + if (static_idmap_child == NULL) { + return NT_STATUS_NO_MEMORY; + } + + subreq = wb_parent_idmap_setup_send(static_idmap_child, + global_event_context()); + if (subreq == NULL) { + /* + * This is only an optimization, so we're free to + * to ignore errors + */ + DBG_ERR("wb_parent_idmap_setup_send() failed\n"); + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, init_idmap_child_done, NULL); + DBG_DEBUG("wb_parent_idmap_setup_send() started\n"); + return NT_STATUS_OK; +} + +static void init_idmap_child_done(struct tevent_req *subreq) +{ + const struct wb_parent_idmap_config *cfg = NULL; + NTSTATUS status; + + status = wb_parent_idmap_setup_recv(subreq, &cfg); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + /* + * This is only an optimization, so we're free to + * to ignore errors + */ + DBG_ERR("wb_parent_idmap_setup_recv() failed %s\n", + nt_errstr(status)); + return; + } + + DBG_DEBUG("wb_parent_idmap_setup_recv() finished\n"); +} + +struct wb_parent_idmap_setup_state { + struct tevent_context *ev; + struct wb_parent_idmap_config *cfg; + size_t dom_idx; +}; + +static void wb_parent_idmap_setup_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct wb_parent_idmap_setup_state *state = + tevent_req_data(req, + struct wb_parent_idmap_setup_state); + + if (req_state == TEVENT_REQ_DONE) { + state->cfg = NULL; + return; + } + + if (state->cfg == NULL) { + return; + } + + state->cfg->num_doms = 0; + state->cfg->initialized = false; + TALLOC_FREE(state->cfg->doms); + state->cfg = NULL; +} + +static void wb_parent_idmap_setup_queue_wait_done(struct tevent_req *subreq); +static bool wb_parent_idmap_setup_scan_config(const char *domname, + void *private_data); +static void wb_parent_idmap_setup_lookupname_next(struct tevent_req *req); +static void wb_parent_idmap_setup_lookupname_done(struct tevent_req *subreq); + +struct tevent_req *wb_parent_idmap_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev) +{ + struct tevent_req *req = NULL; + struct wb_parent_idmap_setup_state *state = NULL; + struct tevent_req *subreq = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct wb_parent_idmap_setup_state); + if (req == NULL) { + return NULL; + } + *state = (struct wb_parent_idmap_setup_state) { + .ev = ev, + .cfg = &static_parent_idmap_config, + .dom_idx = 0, + }; + + if (state->cfg->initialized) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + if (state->cfg->queue == NULL) { + state->cfg->queue = tevent_queue_create(NULL, + "wb_parent_idmap_config_queue"); + if (tevent_req_nomem(state->cfg->queue, req)) { + return tevent_req_post(req, ev); + } + } + + subreq = tevent_queue_wait_send(state, state->ev, state->cfg->queue); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + wb_parent_idmap_setup_queue_wait_done, + req); + + return req; +} + +static void wb_parent_idmap_setup_queue_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct wb_parent_idmap_setup_state *state = + tevent_req_data(req, + struct wb_parent_idmap_setup_state); + bool ok; + + /* + * Note we don't call TALLOC_FREE(subreq) here in order to block the + * queue until tevent_req_received() in wb_parent_idmap_setup_recv() + * will destroy it implicitly. + */ + ok = tevent_queue_wait_recv(subreq); + if (!ok) { + DBG_ERR("tevent_queue_wait_recv() failed\n"); + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + if (state->cfg->num_doms != 0) { + /* + * If we're not the first one we're done. + */ + tevent_req_done(req); + return; + } + + /* + * From this point we start changing state->cfg, + * which is &static_parent_idmap_config, + * so we better setup a cleanup function + * to undo the changes on failure. + */ + tevent_req_set_cleanup_fn(req, wb_parent_idmap_setup_cleanup); + + /* + * Put the passdb idmap domain first. We always need to try + * there first. + */ + state->cfg->doms = talloc_zero_array(NULL, + struct wb_parent_idmap_config_dom, + 1); + if (tevent_req_nomem(state->cfg->doms, req)) { + return; + } + state->cfg->doms[0].low_id = 0; + state->cfg->doms[0].high_id = UINT_MAX; + state->cfg->doms[0].name = talloc_strdup(state->cfg->doms, + get_global_sam_name()); + if (tevent_req_nomem(state->cfg->doms[0].name, req)) { + return; + } + state->cfg->num_doms += 1; + + lp_scan_idmap_domains(wb_parent_idmap_setup_scan_config, req); + if (!tevent_req_is_in_progress(req)) { + return; + } + + wb_parent_idmap_setup_lookupname_next(req); +} + +static bool wb_parent_idmap_setup_scan_config(const char *domname, + void *private_data) +{ + struct tevent_req *req = + talloc_get_type_abort(private_data, + struct tevent_req); + struct wb_parent_idmap_setup_state *state = + tevent_req_data(req, + struct wb_parent_idmap_setup_state); + struct wb_parent_idmap_config_dom *map = NULL; + size_t i; + const char *range; + unsigned low_id, high_id; + int ret; + + range = idmap_config_const_string(domname, "range", NULL); + if (range == NULL) { + DBG_DEBUG("No range for domain %s found\n", domname); + return false; + } + + ret = sscanf(range, "%u - %u", &low_id, &high_id); + if (ret != 2) { + DBG_DEBUG("Invalid range spec \"%s\" for domain %s\n", + range, domname); + return false; + } + + if (low_id > high_id) { + DBG_DEBUG("Invalid range %u - %u for domain %s\n", + low_id, high_id, domname); + return false; + } + + for (i=0; i<state->cfg->num_doms; i++) { + if (strequal(domname, state->cfg->doms[i].name)) { + map = &state->cfg->doms[i]; + break; + } + } + + if (map == NULL) { + struct wb_parent_idmap_config_dom *tmp; + char *name; + + name = talloc_strdup(state, domname); + if (name == NULL) { + DBG_ERR("talloc failed\n"); + return false; + } + + tmp = talloc_realloc( + NULL, state->cfg->doms, struct wb_parent_idmap_config_dom, + state->cfg->num_doms+1); + if (tmp == NULL) { + DBG_ERR("talloc failed\n"); + return false; + } + state->cfg->doms = tmp; + + map = &state->cfg->doms[state->cfg->num_doms]; + state->cfg->num_doms += 1; + ZERO_STRUCTP(map); + map->name = talloc_move(state->cfg->doms, &name); + } + + map->low_id = low_id; + map->high_id = high_id; + + return false; +} + +static void wb_parent_idmap_setup_lookupname_next(struct tevent_req *req) +{ + struct wb_parent_idmap_setup_state *state = + tevent_req_data(req, + struct wb_parent_idmap_setup_state); + struct wb_parent_idmap_config_dom *dom = + &state->cfg->doms[state->dom_idx]; + struct tevent_req *subreq = NULL; + + next_domain: + if (state->dom_idx == state->cfg->num_doms) { + /* + * We're done, so start the idmap child + */ + setup_child(NULL, static_idmap_child, "log.winbindd", "idmap"); + static_parent_idmap_config.initialized = true; + tevent_req_done(req); + return; + } + + if (strequal(dom->name, "*")) { + state->dom_idx++; + goto next_domain; + } + + subreq = wb_lookupname_send(state, + state->ev, + dom->name, + dom->name, + "", + LOOKUP_NAME_NO_NSS); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + wb_parent_idmap_setup_lookupname_done, + req); +} + +static void wb_parent_idmap_setup_lookupname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct wb_parent_idmap_setup_state *state = + tevent_req_data(req, + struct wb_parent_idmap_setup_state); + struct wb_parent_idmap_config_dom *dom = + &state->cfg->doms[state->dom_idx]; + enum lsa_SidType type; + NTSTATUS status; + + status = wb_lookupname_recv(subreq, &dom->sid, &type); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Lookup domain name '%s' failed '%s'\n", + dom->name, + nt_errstr(status)); + + state->dom_idx++; + wb_parent_idmap_setup_lookupname_next(req); + return; + } + + if (type != SID_NAME_DOMAIN) { + struct dom_sid_buf buf; + + DBG_ERR("SID %s for idmap domain name '%s' " + "not a domain SID\n", + dom_sid_str_buf(&dom->sid, &buf), + dom->name); + + ZERO_STRUCT(dom->sid); + } + + state->dom_idx++; + wb_parent_idmap_setup_lookupname_next(req); + + return; +} + +NTSTATUS wb_parent_idmap_setup_recv(struct tevent_req *req, + const struct wb_parent_idmap_config **_cfg) +{ + const struct wb_parent_idmap_config *cfg = &static_parent_idmap_config; + NTSTATUS status; + + *_cfg = NULL; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + /* + * Note state->cfg is already set to NULL by + * wb_parent_idmap_setup_cleanup() + */ + *_cfg = cfg; + tevent_req_received(req); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_irpc.c b/source3/winbindd/winbindd_irpc.c new file mode 100644 index 0000000..f66d797 --- /dev/null +++ b/source3/winbindd/winbindd_irpc.c @@ -0,0 +1,891 @@ +/* + Unix SMB/CIFS implementation. + async implementation of commands submitted over IRPC + Copyright (C) Volker Lendecke 2009 + Copyright (C) Guenther Deschner 2009 + Copyright (C) Andrew Bartlett 2014 + Copyright (C) Andrew Tridgell 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "source4/lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_winbind.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "libcli/security/dom_sid.h" +#include "passdb/lookup_sid.h" /* only for LOOKUP_NAME_NO_NSS flag */ +#include "librpc/gen_ndr/ndr_irpc.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "lib/global_contexts.h" +#include "lib/param/param.h" +#include "messages.h" + +struct imessaging_context *winbind_imessaging_context(void) +{ + static struct imessaging_context *msg = NULL; + struct messaging_context *msg_ctx; + struct server_id myself; + struct loadparm_context *lp_ctx; + + if (msg != NULL) { + return msg; + } + + msg_ctx = global_messaging_context(); + if (msg_ctx == NULL) { + smb_panic("global_messaging_context failed\n"); + } + myself = messaging_server_id(msg_ctx); + + lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers()); + if (lp_ctx == NULL) { + smb_panic("Could not load smb.conf to init winbindd's imessaging context.\n"); + } + + /* + * Note we MUST use the NULL context here, not the autofree context, + * to avoid side effects in forked children exiting. + */ + msg = imessaging_init(NULL, lp_ctx, myself, global_event_context()); + talloc_unlink(NULL, lp_ctx); + + if (msg == NULL) { + smb_panic("Could not init winbindd's messaging context.\n"); + } + return msg; +} + +struct wb_irpc_forward_state { + struct irpc_message *msg; + const char *opname; + struct dcesrv_call_state *dce_call; +}; + +/* + called when the forwarded rpc request is finished + */ +static void wb_irpc_forward_callback(struct tevent_req *subreq) +{ + struct wb_irpc_forward_state *st = + tevent_req_callback_data(subreq, + struct wb_irpc_forward_state); + const char *opname = st->opname; + NTSTATUS status; + + status = dcerpc_binding_handle_call_recv(subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("RPC callback failed for %s - %s\n", + opname, nt_errstr(status))); + irpc_send_reply(st->msg, status); + return; + } + + irpc_send_reply(st->msg, status); +} + + + +/** + * Forward a RPC call using IRPC to another task + */ + +static NTSTATUS wb_irpc_forward_rpc_call(struct irpc_message *msg, TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *r, uint32_t callid, + const char *opname, + struct winbindd_domain *domain, + uint32_t timeout) +{ + struct wb_irpc_forward_state *st; + struct dcerpc_binding_handle *binding_handle; + struct tevent_req *subreq; + + st = talloc(mem_ctx, struct wb_irpc_forward_state); + if (st == NULL) { + return NT_STATUS_NO_MEMORY; + } + + st->msg = msg; + st->opname = opname; + + binding_handle = dom_child_handle(domain); + if (binding_handle == NULL) { + DEBUG(0,("%s: Failed to forward request to winbind handler for %s\n", + opname, domain->name)); + return NT_STATUS_UNSUCCESSFUL; + } + + /* reset timeout for the handle */ + dcerpc_binding_handle_set_timeout(binding_handle, timeout); + + /* forward the call */ + subreq = dcerpc_binding_handle_call_send(st, ev, + binding_handle, + NULL, &ndr_table_winbind, + callid, + msg, r); + if (subreq == NULL) { + DEBUG(0,("%s: Failed to forward request to winbind handler for %s\n", + opname, domain->name)); + return NT_STATUS_UNSUCCESSFUL; + } + + /* mark the request as replied async */ + msg->defer_reply = true; + + /* setup the callback */ + tevent_req_set_callback(subreq, wb_irpc_forward_callback, st); + return NT_STATUS_OK; +} + +static NTSTATUS wb_irpc_DsrUpdateReadOnlyServerDnsRecords(struct irpc_message *msg, + struct winbind_DsrUpdateReadOnlyServerDnsRecords *req) +{ + struct winbindd_domain *domain = find_our_domain(); + if (domain == NULL) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + DEBUG(5, ("wb_irpc_DsrUpdateReadOnlyServerDnsRecords called\n")); + + return wb_irpc_forward_rpc_call(msg, msg, + global_event_context(), + req, NDR_WINBIND_DSRUPDATEREADONLYSERVERDNSRECORDS, + "winbind_DsrUpdateReadOnlyServerDnsRecords", + domain, IRPC_CALL_TIMEOUT); +} + +static NTSTATUS wb_irpc_SamLogon(struct irpc_message *msg, + struct winbind_SamLogon *req) +{ + struct winbindd_domain *domain; + struct netr_IdentityInfo *identity_info; + const char *target_domain_name = NULL; + const char *account_name = NULL; + + /* + * Make sure we start with authoritative=true, + * it will only set to false if we don't know the + * domain. + */ + req->out.authoritative = true; + + switch (req->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + if (req->in.logon.password == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + identity_info = &req->in.logon.password->identity_info; + break; + + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + if (req->in.logon.network == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + identity_info = &req->in.logon.network->identity_info; + break; + + case NetlogonGenericInformation: + if (req->in.logon.generic == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + identity_info = &req->in.logon.generic->identity_info; + break; + + default: + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + target_domain_name = identity_info->domain_name.string; + if (target_domain_name == NULL) { + target_domain_name = ""; + } + + account_name = identity_info->account_name.string; + if (account_name == NULL) { + account_name = ""; + } + + if (IS_DC && target_domain_name[0] == '\0') { + const char *p = NULL; + + p = strchr_m(account_name, '@'); + if (p != NULL) { + target_domain_name = p + 1; + } + } + + if (IS_DC && target_domain_name[0] == '\0') { + DBG_ERR("target_domain[%s] account[%s]\n", + target_domain_name, account_name); + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + domain = find_auth_domain(0, target_domain_name); + if (domain == NULL) { + DBG_INFO("target_domain[%s] for account[%s] not known\n", + target_domain_name, account_name); + req->out.result = NT_STATUS_NO_SUCH_USER; + req->out.authoritative = 0; + return NT_STATUS_OK; + } + + DEBUG(5, ("wb_irpc_SamLogon called\n")); + + return wb_irpc_forward_rpc_call(msg, msg, + global_event_context(), + req, NDR_WINBIND_SAMLOGON, + "winbind_SamLogon", + domain, IRPC_CALL_TIMEOUT); +} + +static NTSTATUS wb_irpc_LogonControl(struct irpc_message *msg, + struct winbind_LogonControl *req) +{ + TALLOC_CTX *frame = talloc_stackframe(); + char *domain_name = NULL; + struct winbindd_domain *domain = NULL; + + DEBUG(5, ("wb_irpc_LogonControl called\n")); + + switch (req->in.function_code) { + case NETLOGON_CONTROL_REDISCOVER: + case NETLOGON_CONTROL_TC_QUERY: + case NETLOGON_CONTROL_CHANGE_PASSWORD: + case NETLOGON_CONTROL_TC_VERIFY: + if (req->in.data->domain == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + domain_name = talloc_strdup(frame, req->in.data->domain); + if (domain_name == NULL) { + req->out.result = WERR_NOT_ENOUGH_MEMORY; + TALLOC_FREE(frame); + return NT_STATUS_OK; + } + + break; + default: + TALLOC_FREE(frame); + return NT_STATUS_NOT_IMPLEMENTED; + } + + if (req->in.function_code == NETLOGON_CONTROL_REDISCOVER) { + char *p = NULL; + + /* + * NETLOGON_CONTROL_REDISCOVER + * gets an optional \dcname appended to the domain name + */ + p = strchr_m(domain_name, '\\'); + if (p != NULL) { + *p = '\0'; + } + } + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + req->out.result = WERR_NO_SUCH_DOMAIN; + TALLOC_FREE(frame); + return NT_STATUS_OK; + } + + TALLOC_FREE(frame); + return wb_irpc_forward_rpc_call(msg, msg, + global_event_context(), + req, NDR_WINBIND_LOGONCONTROL, + "winbind_LogonControl", + domain, 45 /* timeout */); +} + +static NTSTATUS wb_irpc_GetForestTrustInformation(struct irpc_message *msg, + struct winbind_GetForestTrustInformation *req) +{ + struct winbindd_domain *domain = NULL; + + if (req->in.trusted_domain_name == NULL) { + req->out.result = WERR_NO_SUCH_DOMAIN; + return NT_STATUS_OK; + } + + domain = find_trust_from_name_noinit(req->in.trusted_domain_name); + if (domain == NULL) { + req->out.result = WERR_NO_SUCH_DOMAIN; + return NT_STATUS_OK; + } + + /* + * checking for domain->internal and domain->primary + * makes sure we only do some work when running as DC. + */ + + if (domain->internal) { + req->out.result = WERR_NO_SUCH_DOMAIN; + return NT_STATUS_OK; + } + + if (domain->primary) { + req->out.result = WERR_NO_SUCH_DOMAIN; + return NT_STATUS_OK; + } + + DEBUG(5, ("wb_irpc_GetForestTrustInformation called\n")); + + return wb_irpc_forward_rpc_call(msg, msg, + global_event_context(), + req, NDR_WINBIND_GETFORESTTRUSTINFORMATION, + "winbind_GetForestTrustInformation", + domain, 45 /* timeout */); +} + +static NTSTATUS wb_irpc_SendToSam(struct irpc_message *msg, + struct winbind_SendToSam *req) +{ + /* TODO make sure that it is RWDC */ + struct winbindd_domain *domain = find_our_domain(); + if (domain == NULL) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + DEBUG(5, ("wb_irpc_SendToSam called\n")); + + return wb_irpc_forward_rpc_call(msg, msg, + global_event_context(), + req, NDR_WINBIND_SENDTOSAM, + "winbind_SendToSam", + domain, IRPC_CALL_TIMEOUT); +} + +struct wb_irpc_lsa_LookupSids3_state { + struct irpc_message *msg; + struct lsa_LookupSids3 *req; +}; + +static void wb_irpc_lsa_LookupSids3_done(struct tevent_req *subreq); + +static NTSTATUS wb_irpc_lsa_LookupSids3_call(struct irpc_message *msg, + struct lsa_LookupSids3 *req) +{ + struct wb_irpc_lsa_LookupSids3_state *state = NULL; + struct tevent_req *subreq = NULL; + struct dom_sid *sids = NULL; + uint32_t i; + + state = talloc_zero(msg, struct wb_irpc_lsa_LookupSids3_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->msg = msg; + state->req = req; + + state->req->out.domains = talloc_zero(state->msg, + struct lsa_RefDomainList *); + if (state->req->out.domains == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->req->out.names = talloc_zero(state->msg, + struct lsa_TransNameArray2); + if (state->req->out.names == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->req->out.count = talloc_zero(state->msg, uint32_t); + if (state->req->out.count == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->req->out.names->names = talloc_zero_array(state->msg, + struct lsa_TranslatedName2, + req->in.sids->num_sids); + if (state->req->out.names->names == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sids = talloc_zero_array(state, struct dom_sid, + req->in.sids->num_sids); + if (sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < req->in.sids->num_sids; i++) { + if (req->in.sids->sids[i].sid == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + sids[i] = *req->in.sids->sids[i].sid; + } + + subreq = wb_lookupsids_send(msg, + global_event_context(), + sids, req->in.sids->num_sids); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, wb_irpc_lsa_LookupSids3_done, state); + msg->defer_reply = true; + + return NT_STATUS_OK; +} + +static void wb_irpc_lsa_LookupSids3_done(struct tevent_req *subreq) +{ + struct wb_irpc_lsa_LookupSids3_state *state = + tevent_req_callback_data(subreq, + struct wb_irpc_lsa_LookupSids3_state); + struct lsa_RefDomainList *domains = NULL; + struct lsa_TransNameArray *names = NULL; + NTSTATUS status; + uint32_t i; + + status = wb_lookupsids_recv(subreq, state->msg, + &domains, &names); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("RPC callback failed for %s - %s\n", + __func__, nt_errstr(status))); + irpc_send_reply(state->msg, status); + return; + } + + if (names->count > state->req->in.sids->num_sids) { + status = NT_STATUS_INTERNAL_ERROR; + DEBUG(0,("RPC callback failed for %s - %s\n", + __func__, nt_errstr(status))); + irpc_send_reply(state->msg, status); + return; + } + + *state->req->out.domains = domains; + for (i = 0; i < names->count; i++) { + struct lsa_TranslatedName2 *n2 = + &state->req->out.names->names[i]; + + n2->sid_type = names->names[i].sid_type; + n2->name = names->names[i].name; + n2->sid_index = names->names[i].sid_index; + n2->unknown = 0; + + if (n2->sid_type != SID_NAME_UNKNOWN) { + (*state->req->out.count)++; + } + } + state->req->out.names->count = names->count; + + if (*state->req->out.count == 0) { + state->req->out.result = NT_STATUS_NONE_MAPPED; + } else if (*state->req->out.count != names->count) { + state->req->out.result = NT_STATUS_SOME_NOT_MAPPED; + } else { + state->req->out.result = NT_STATUS_OK; + } + + irpc_send_reply(state->msg, NT_STATUS_OK); + return; +} + +struct wb_irpc_lsa_LookupNames4_name { + void *state; + uint32_t idx; + const char *namespace; + const char *domain; + char *name; + struct dom_sid sid; + enum lsa_SidType type; + struct dom_sid *authority_sid; +}; + +struct wb_irpc_lsa_LookupNames4_state { + struct irpc_message *msg; + struct lsa_LookupNames4 *req; + struct wb_irpc_lsa_LookupNames4_name *names; + uint32_t num_pending; + uint32_t num_domain_sids; + struct dom_sid *domain_sids; +}; + +static void wb_irpc_lsa_LookupNames4_done(struct tevent_req *subreq); + +static NTSTATUS wb_irpc_lsa_LookupNames4_call(struct irpc_message *msg, + struct lsa_LookupNames4 *req) +{ + struct wb_irpc_lsa_LookupNames4_state *state = NULL; + struct tevent_req *subreq = NULL; + uint32_t i; + + + state = talloc_zero(msg, struct wb_irpc_lsa_LookupNames4_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->msg = msg; + state->req = req; + + state->req->out.domains = talloc_zero(state->msg, + struct lsa_RefDomainList *); + if (state->req->out.domains == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->req->out.sids = talloc_zero(state->msg, + struct lsa_TransSidArray3); + if (state->req->out.sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->req->out.count = talloc_zero(state->msg, uint32_t); + if (state->req->out.count == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->req->out.sids->sids = talloc_zero_array(state->msg, + struct lsa_TranslatedSid3, + req->in.num_names); + if (state->req->out.sids->sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->names = talloc_zero_array(state, + struct wb_irpc_lsa_LookupNames4_name, + req->in.num_names); + if (state->names == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < req->in.num_names; i++) { + struct wb_irpc_lsa_LookupNames4_name *nstate = + &state->names[i]; + char *p = NULL; + + if (req->in.names[i].string == NULL) { + DBG_ERR("%s: name[%s] NT_STATUS_REQUEST_NOT_ACCEPTED.\n", + __location__, req->in.names[i].string); + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + nstate->state = state; + nstate->idx = i; + nstate->name = talloc_strdup(state->names, + req->in.names[i].string); + if (nstate->name == NULL) { + return NT_STATUS_NO_MEMORY; + } + nstate->type = SID_NAME_UNKNOWN; + + /* cope with the name being a fully qualified name */ + p = strchr(nstate->name, '\\'); + if (p != NULL) { + *p = 0; + nstate->domain = nstate->name; + nstate->namespace = nstate->domain; + nstate->name = p+1; + } else if ((p = strchr(nstate->name, '@')) != NULL) { + /* upn */ + nstate->domain = ""; + nstate->namespace = p + 1; + } else { + /* + * TODO: select the domain based on + * req->in.level and req->in.client_revision + * + * For now we don't allow this. + */ + DBG_ERR("%s: name[%s] NT_STATUS_REQUEST_NOT_ACCEPTED.\n", + __location__, nstate->name); + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + subreq = wb_lookupname_send(msg, + global_event_context(), + nstate->namespace, + nstate->domain, + nstate->name, + LOOKUP_NAME_NO_NSS); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, + wb_irpc_lsa_LookupNames4_done, + nstate); + state->num_pending++; + } + + msg->defer_reply = true; + + return NT_STATUS_OK; +} + +static void wb_irpc_lsa_LookupNames4_domains_done(struct tevent_req *subreq); + +static void wb_irpc_lsa_LookupNames4_done(struct tevent_req *subreq) +{ + struct wb_irpc_lsa_LookupNames4_name *nstate = + (struct wb_irpc_lsa_LookupNames4_name *) + tevent_req_callback_data_void(subreq); + struct wb_irpc_lsa_LookupNames4_state *state = + talloc_get_type_abort(nstate->state, + struct wb_irpc_lsa_LookupNames4_state); + struct dom_sid_buf buf; + NTSTATUS status; + + SMB_ASSERT(state->num_pending > 0); + state->num_pending--; + status = wb_lookupname_recv(subreq, &nstate->sid, &nstate->type); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("RPC callback failed for %s - %s\n", + __func__, nt_errstr(status))); + irpc_send_reply(state->msg, status); + return; + } + + status = dom_sid_split_rid(state, &nstate->sid, + &nstate->authority_sid, NULL); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("dom_sid_split_rid(%s) failed - %s\n", + dom_sid_str_buf(&nstate->sid, &buf), + nt_errstr(status)); + irpc_send_reply(state->msg, status); + return; + } + + status = add_sid_to_array_unique(state, + nstate->authority_sid, + &state->domain_sids, + &state->num_domain_sids); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("add_sid_to_array_unique(%s) failed - %s\n", + dom_sid_str_buf(nstate->authority_sid, &buf), + nt_errstr(status)); + irpc_send_reply(state->msg, status); + return; + } + + if (state->num_pending > 0) { + /* + * wait for more... + */ + return; + } + + /* + * Now resolve all domains back to a name + * to get a good lsa_RefDomainList + */ + subreq = wb_lookupsids_send(state, + global_event_context(), + state->domain_sids, + state->num_domain_sids); + if (subreq == NULL) { + status = NT_STATUS_NO_MEMORY; + DBG_ERR("wb_lookupsids_send - %s\n", + nt_errstr(status)); + irpc_send_reply(state->msg, status); + return; + } + tevent_req_set_callback(subreq, + wb_irpc_lsa_LookupNames4_domains_done, + state); + + return; +} + +static void wb_irpc_lsa_LookupNames4_domains_done(struct tevent_req *subreq) +{ + struct wb_irpc_lsa_LookupNames4_state *state = + tevent_req_callback_data(subreq, + struct wb_irpc_lsa_LookupNames4_state); + struct lsa_RefDomainList *domains = NULL; + struct lsa_TransNameArray *names = NULL; + NTSTATUS status; + uint32_t i; + + status = wb_lookupsids_recv(subreq, state->msg, + &domains, &names); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("RPC callback failed for %s - %s\n", + __func__, nt_errstr(status))); + irpc_send_reply(state->msg, status); + return; + } + + *state->req->out.domains = domains; + for (i = 0; i < state->req->in.num_names; i++) { + struct wb_irpc_lsa_LookupNames4_name *nstate = + &state->names[i]; + struct lsa_TranslatedSid3 *s3 = + &state->req->out.sids->sids[i]; + uint32_t di; + + s3->sid_type = nstate->type; + if (s3->sid_type != SID_NAME_UNKNOWN) { + s3->sid = &nstate->sid; + } else { + s3->sid = NULL; + } + s3->sid_index = UINT32_MAX; + for (di = 0; di < domains->count; di++) { + bool match; + + if (domains->domains[di].sid == NULL) { + continue; + } + + match = dom_sid_equal(nstate->authority_sid, + domains->domains[di].sid); + if (match) { + s3->sid_index = di; + break; + } + } + if (s3->sid_type != SID_NAME_UNKNOWN) { + (*state->req->out.count)++; + } + } + state->req->out.sids->count = state->req->in.num_names; + + if (*state->req->out.count == 0) { + state->req->out.result = NT_STATUS_NONE_MAPPED; + } else if (*state->req->out.count != state->req->in.num_names) { + state->req->out.result = NT_STATUS_SOME_NOT_MAPPED; + } else { + state->req->out.result = NT_STATUS_OK; + } + + irpc_send_reply(state->msg, NT_STATUS_OK); + return; +} + +struct wb_irpc_GetDCName_state { + struct irpc_message *msg; + struct wbint_DsGetDcName *req; +}; + +static void wb_irpc_GetDCName_done(struct tevent_req *subreq); + +static NTSTATUS wb_irpc_GetDCName(struct irpc_message *msg, + struct wbint_DsGetDcName *req) +{ + + struct tevent_req *subreq = NULL; + struct wb_irpc_GetDCName_state *state = NULL; + + state = talloc_zero(msg, struct wb_irpc_GetDCName_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->msg = msg; + state->req = req; + + subreq = wb_dsgetdcname_send(msg, + global_event_context(), + req->in.domain_name, + req->in.domain_guid, + req->in.site_name, + req->in.flags); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + + tevent_req_set_callback(subreq, + wb_irpc_GetDCName_done, + state); + + msg->defer_reply = true; + + return NT_STATUS_OK; +} + +static void wb_irpc_GetDCName_done(struct tevent_req *subreq) +{ + struct wb_irpc_GetDCName_state *state = tevent_req_callback_data( + subreq, struct wb_irpc_GetDCName_state); + NTSTATUS status; + + status = wb_dsgetdcname_recv(subreq, state->msg, + state->req->out.dc_info); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("RPC callback failed for %s - %s\n", "DSGETDCNAME", + nt_errstr(status)); + } + + state->req->out.result = status; + + irpc_send_reply(state->msg, NT_STATUS_OK); +} + +NTSTATUS wb_irpc_register(void) +{ + NTSTATUS status; + + status = IRPC_REGISTER(winbind_imessaging_context(), winbind, WINBIND_DSRUPDATEREADONLYSERVERDNSRECORDS, + wb_irpc_DsrUpdateReadOnlyServerDnsRecords, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = IRPC_REGISTER(winbind_imessaging_context(), winbind, WINBIND_SAMLOGON, + wb_irpc_SamLogon, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = IRPC_REGISTER(winbind_imessaging_context(), winbind, + WINBIND_LOGONCONTROL, + wb_irpc_LogonControl, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = IRPC_REGISTER(winbind_imessaging_context(), winbind, + WINBIND_GETFORESTTRUSTINFORMATION, + wb_irpc_GetForestTrustInformation, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = IRPC_REGISTER(winbind_imessaging_context(), winbind, WINBIND_SENDTOSAM, + wb_irpc_SendToSam, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = IRPC_REGISTER(winbind_imessaging_context(), + lsarpc, LSA_LOOKUPSIDS3, + wb_irpc_lsa_LookupSids3_call, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = IRPC_REGISTER(winbind_imessaging_context(), + lsarpc, LSA_LOOKUPNAMES4, + wb_irpc_lsa_LookupNames4_call, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = IRPC_REGISTER(winbind_imessaging_context(), + winbind, WBINT_DSGETDCNAME, + wb_irpc_GetDCName, NULL); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_list_groups.c b/source3/winbindd/winbindd_list_groups.c new file mode 100644 index 0000000..272c638 --- /dev/null +++ b/source3/winbindd/winbindd_list_groups.c @@ -0,0 +1,233 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_LIST_GROUPS + Copyright (C) Volker Lendecke 2009 + + 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 "util/debug.h" +#include "winbindd.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_list_groups_domstate { + struct tevent_req *subreq; + struct winbindd_domain *domain; + struct wbint_Principals groups; +}; + +struct winbindd_list_groups_state { + uint32_t num_received; + /* All domains */ + uint32_t num_domains; + struct winbindd_list_groups_domstate *domains; +}; + +static void winbindd_list_groups_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_list_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req; + struct winbindd_list_groups_state *state; + struct winbindd_domain *domain; + uint32_t i; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_list_groups_state); + if (req == NULL) { + return NULL; + } + + D_NOTICE("[%s (%u)] Winbind external command LIST_GROUPS start.\n" + "WBFLAG_FROM_NSS is %s, winbind enum groups is %d.\n", + cli->client_name, + (unsigned int)cli->pid, + request->wb_flags & WBFLAG_FROM_NSS ? "Set" : "Unset", + lp_winbind_enum_groups()); + + if (request->wb_flags & WBFLAG_FROM_NSS && !lp_winbind_enum_groups()) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + /* Ensure null termination */ + request->domain_name[sizeof(request->domain_name)-1]='\0'; + + if (request->domain_name[0] != '\0') { + state->num_domains = 1; + D_DEBUG("List groups for domain %s.\n", request->domain_name); + } else { + state->num_domains = 0; + for (domain = domain_list(); domain; domain = domain->next) { + state->num_domains += 1; + } + D_DEBUG("List groups for %"PRIu32" domain(s).\n", state->num_domains); + } + + state->domains = talloc_array(state, + struct winbindd_list_groups_domstate, + state->num_domains); + if (tevent_req_nomem(state->domains, req)) { + return tevent_req_post(req, ev); + } + + if (request->domain_name[0] != '\0') { + ZERO_STRUCT(state->domains[0].groups); + + state->domains[0].domain = find_domain_from_name_noinit( + request->domain_name); + if (state->domains[0].domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + } else { + i = 0; + for (domain = domain_list(); domain; domain = domain->next) { + ZERO_STRUCT(state->domains[i].groups); + + state->domains[i].domain = domain; + i++; + } + } + + for (i=0; i<state->num_domains; i++) { + struct winbindd_list_groups_domstate *d = &state->domains[i]; + + d->subreq = dcerpc_wbint_QueryGroupList_send( + state->domains, ev, dom_child_handle(d->domain), + &d->groups); + if (tevent_req_nomem(d->subreq, req)) { + TALLOC_FREE(state->domains); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(d->subreq, winbindd_list_groups_done, + req); + } + state->num_received = 0; + return req; +} + +static void winbindd_list_groups_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_list_groups_state *state = tevent_req_data( + req, struct winbindd_list_groups_state); + NTSTATUS status, result; + uint32_t i; + + status = dcerpc_wbint_QueryGroupList_recv(subreq, state->domains, + &result); + + for (i=0; i<state->num_domains; i++) { + if (subreq == state->domains[i].subreq) { + break; + } + } + if (i < state->num_domains) { + struct winbindd_list_groups_domstate *d = &state->domains[i]; + + D_DEBUG("Domain %s returned %"PRIu32" groups\n", d->domain->name, + d->groups.num_principals); + + d->subreq = NULL; + + if (!NT_STATUS_IS_OK(status) || !NT_STATUS_IS_OK(result)) { + D_WARNING("list_groups for domain %s failed\n", + d->domain->name); + d->groups.num_principals = 0; + } + } + + TALLOC_FREE(subreq); + + state->num_received += 1; + + if (state->num_received >= state->num_domains) { + tevent_req_done(req); + } +} + +NTSTATUS winbindd_list_groups_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_list_groups_state *state = tevent_req_data( + req, struct winbindd_list_groups_state); + NTSTATUS status; + char *result; + uint32_t i, j, num_entries = 0; + size_t len; + + D_NOTICE("Winbind external command LIST_GROUPS end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + + len = 0; + response->data.num_entries = 0; + for (i=0; i<state->num_domains; i++) { + struct winbindd_list_groups_domstate *d = &state->domains[i]; + + for (j=0; j<d->groups.num_principals; j++) { + const char *name; + name = fill_domain_username_talloc(response, d->domain->name, + d->groups.principals[j].name, + True); + if (name == NULL) { + return NT_STATUS_NO_MEMORY; + } + len += strlen(name)+1; + } + response->data.num_entries += d->groups.num_principals; + } + + result = talloc_array(response, char, len+1); + if (result == 0) { + return NT_STATUS_NO_MEMORY; + } + + len = 0; + for (i=0; i<state->num_domains; i++) { + struct winbindd_list_groups_domstate *d = &state->domains[i]; + + for (j=0; j<d->groups.num_principals; j++) { + const char *name; + size_t this_len; + name = fill_domain_username_talloc(response, d->domain->name, + d->groups.principals[j].name, + True); + if (name == NULL) { + return NT_STATUS_NO_MEMORY; + } + this_len = strlen(name); + memcpy(result+len, name, this_len); + len += this_len; + result[len] = ','; + len += 1; + num_entries++; + } + } + result[len-1] = '\0'; + + response->data.num_entries = num_entries; + response->extra_data.data = result; + response->length += len; + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_list_users.c b/source3/winbindd/winbindd_list_users.c new file mode 100644 index 0000000..8630672 --- /dev/null +++ b/source3/winbindd/winbindd_list_users.c @@ -0,0 +1,216 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_LIST_USERS + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "lib/util/strv.h" + +struct winbindd_list_users_domstate { + struct tevent_req *subreq; + struct winbindd_domain *domain; + char *users; +}; + +struct winbindd_list_users_state { + size_t num_received; + /* All domains */ + size_t num_domains; + struct winbindd_list_users_domstate *domains; +}; + +static void winbindd_list_users_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_list_users_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req; + struct winbindd_list_users_state *state; + struct winbindd_domain *domain; + size_t i; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_list_users_state); + if (req == NULL) { + return NULL; + } + D_NOTICE("[%s (%u)] Winbind external command LIST_USERS start.\n" + "WBFLAG_FROM_NSS is %s, winbind enum users is %d.\n", + cli->client_name, + (unsigned int)cli->pid, + request->wb_flags & WBFLAG_FROM_NSS ? "Set" : "Unset", + lp_winbind_enum_users()); + + if (request->wb_flags & WBFLAG_FROM_NSS && !lp_winbind_enum_users()) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + /* Ensure null termination */ + request->domain_name[sizeof(request->domain_name)-1]='\0'; + + D_NOTICE("Listing users for domain %s\n", request->domain_name); + if (request->domain_name[0] != '\0') { + state->num_domains = 1; + } else { + state->num_domains = 0; + for (domain = domain_list(); domain; domain = domain->next) { + state->num_domains += 1; + } + } + + state->domains = talloc_array(state, + struct winbindd_list_users_domstate, + state->num_domains); + if (tevent_req_nomem(state->domains, req)) { + return tevent_req_post(req, ev); + } + + if (request->domain_name[0] != '\0') { + state->domains[0].domain = find_domain_from_name_noinit( + request->domain_name); + if (state->domains[0].domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + } else { + i = 0; + for (domain = domain_list(); domain; domain = domain->next) { + state->domains[i++].domain = domain; + } + } + + for (i=0; i<state->num_domains; i++) { + struct winbindd_list_users_domstate *d = &state->domains[i]; + /* + * Use "state" as a talloc memory context since it has type + * "struct tevent_req". This is needed to make tevent call depth + * tracking working as expected. + * After calling wb_query_user_list_send(), re-parent back to + * "state->domains" to make TALLOC_FREE(state->domains) working. + */ + d->subreq = wb_query_user_list_send(state, ev, d->domain); + d->subreq = talloc_reparent(state, state->domains, d->subreq); + if (tevent_req_nomem(d->subreq, req)) { + TALLOC_FREE(state->domains); + return tevent_req_post(req, ev); + } + tevent_req_set_callback(d->subreq, winbindd_list_users_done, + req); + } + state->num_received = 0; + return req; +} + +static void winbindd_list_users_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_list_users_state *state = tevent_req_data( + req, struct winbindd_list_users_state); + struct winbindd_list_users_domstate *d; + NTSTATUS status; + size_t i; + + for (i=0; i<state->num_domains; i++) { + if (subreq == state->domains[i].subreq) { + break; + } + } + if (i == state->num_domains) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + d = &state->domains[i]; + + status = wb_query_user_list_recv(subreq, state->domains, + &d->users); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + /* + * Just skip this domain + */ + d->users = NULL; + } + + state->num_received += 1; + + if (state->num_received >= state->num_domains) { + tevent_req_done(req); + } +} + +NTSTATUS winbindd_list_users_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_list_users_state *state = tevent_req_data( + req, struct winbindd_list_users_state); + NTSTATUS status; + char *result; + size_t i, len; + + D_NOTICE("Winbind external command LIST_USERS end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Failed with %s.\n", nt_errstr(status)); + return status; + } + + result = NULL; + + for (i=0; i<state->num_domains; i++) { + struct winbindd_list_users_domstate *d = &state->domains[i]; + int ret; + + if (d->users == NULL) { + continue; + } + + ret = strv_append(state, &result, d->users); + if (ret != 0) { + return map_nt_error_from_unix(ret); + } + } + + len = talloc_get_size(result); + + response->extra_data.data = talloc_steal(response, result); + response->length += len; + response->data.num_entries = 0; + + if (result != NULL && len >= 1) { + len -= 1; + response->data.num_entries = 1; + + for (i=0; i<len; i++) { + if (result[i] == '\0') { + result[i] = ','; + response->data.num_entries += 1; + } + } + } + + D_NOTICE("Got %"PRIu32" user(s):\n%s\n", + response->data.num_entries, + result); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_locator.c b/source3/winbindd/winbindd_locator.c new file mode 100644 index 0000000..c915bf2 --- /dev/null +++ b/source3/winbindd/winbindd_locator.c @@ -0,0 +1,56 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - miscellaneous other functions + + Copyright (C) Tim Potter 2000 + Copyright (C) Andrew Bartlett 2002 + + 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.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + + +static struct winbindd_child *static_locator_child = NULL; + +struct winbindd_child *locator_child(void) +{ + return static_locator_child; +} + +struct dcerpc_binding_handle *locator_child_handle(void) +{ + return static_locator_child->binding_handle; +} + +NTSTATUS init_locator_child(TALLOC_CTX *mem_ctx) +{ + if (static_locator_child != NULL) { + DBG_ERR("locator child already allocated\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + static_locator_child = talloc_zero(mem_ctx, struct winbindd_child); + if (static_locator_child == NULL) { + return NT_STATUS_NO_MEMORY; + } + + setup_child(NULL, static_locator_child, "log.winbindd", "locator"); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_lookupname.c b/source3/winbindd/winbindd_lookupname.c new file mode 100644 index 0000000..f7af104 --- /dev/null +++ b/source3/winbindd/winbindd_lookupname.c @@ -0,0 +1,130 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_LOOKUPNAME + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "libcli/security/dom_sid.h" + +struct winbindd_lookupname_state { + struct tevent_context *ev; + struct dom_sid sid; + enum lsa_SidType type; +}; + +static void winbindd_lookupname_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_lookupname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_lookupname_state *state; + char *p = NULL; + const char *domname = NULL; + const char *name = NULL; + const char *namespace = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_lookupname_state); + if (req == NULL) { + return NULL; + } + + D_NOTICE("[%s (%u)] Winbind external command LOOKUPNAME start.\n", + cli->client_name, + (unsigned int)cli->pid); + + state->ev = ev; + + /* Ensure null termination */ + request->data.name.dom_name[ + sizeof(request->data.name.dom_name)-1]='\0'; + request->data.name.name[sizeof(request->data.name.name)-1]='\0'; + + if (strlen(request->data.name.dom_name) == 0) { + /* cope with the name being a fully qualified name */ + p = strstr(request->data.name.name, lp_winbind_separator()); + if (p != NULL) { + *p = '\0'; + domname = request->data.name.name; + namespace = domname; + name = p + 1; + } else { + p = strchr(request->data.name.name, '@'); + if (p != NULL) { + /* upn */ + namespace = p + 1; + } else { + namespace = ""; + } + domname = ""; + name = request->data.name.name; + } + } else { + domname = request->data.name.dom_name; + namespace = domname; + name = request->data.name.name; + } + + D_NOTICE("lookupname %s%s%s\n", domname, lp_winbind_separator(), name); + + subreq = wb_lookupname_send(state, ev, namespace, domname, name, 0); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_lookupname_done, req); + return req; +} + +static void winbindd_lookupname_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_lookupname_state *state = tevent_req_data( + req, struct winbindd_lookupname_state); + NTSTATUS status; + + status = wb_lookupname_recv(subreq, &state->sid, &state->type); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_lookupname_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_lookupname_state *state = tevent_req_data( + req, struct winbindd_lookupname_state); + NTSTATUS status; + + D_NOTICE("Winbind external command LOOKUPNAME end.\n"); + if (tevent_req_is_nterror(req, &status)) { + struct dom_sid_buf buf; + D_WARNING("Could not convert SID %s, error is %s\n", + dom_sid_str_buf(&state->sid, &buf), + nt_errstr(status)); + return status; + } + sid_to_fstring(response->data.sid.sid, &state->sid); + response->data.sid.type = state->type; + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_lookuprids.c b/source3/winbindd/winbindd_lookuprids.c new file mode 100644 index 0000000..fc8fa46 --- /dev/null +++ b/source3/winbindd/winbindd_lookuprids.c @@ -0,0 +1,200 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_LOOKUPRIDS + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "../libcli/security/security.h" +#include "lib/util/smb_strtox.h" +#include "lib/util/string_wrappers.h" + +struct winbindd_lookuprids_state { + struct tevent_context *ev; + struct dom_sid domain_sid; + const char *domain_name; + struct wbint_RidArray rids; + struct wbint_Principals names; +}; + +static bool parse_ridlist(TALLOC_CTX *mem_ctx, char *ridstr, + uint32_t **prids, uint32_t *pnum_rids); + +static void winbindd_lookuprids_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_lookuprids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_lookuprids_state *state; + struct winbindd_domain *domain; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_lookuprids_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + /* Ensure null termination */ + request->data.sid[sizeof(request->data.sid)-1]='\0'; + + DEBUG(3, ("lookuprids (%s)\n", request->data.sid)); + + if (!string_to_sid(&state->domain_sid, request->data.sid)) { + DEBUG(5, ("%s not a SID\n", request->data.sid)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + domain = find_lookup_domain_from_sid(&state->domain_sid); + if (domain == NULL) { + struct dom_sid_buf buf; + DEBUG(5, ("Domain for sid %s not found\n", + dom_sid_str_buf(&state->domain_sid, &buf))); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + + if (request->extra_data.data[request->extra_len-1] != '\0') { + DEBUG(5, ("extra_data not 0-terminated\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + if (!parse_ridlist(state, request->extra_data.data, + &state->rids.rids, &state->rids.num_rids)) { + DEBUG(5, ("parse_ridlist failed\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_LookupRids_send( + state, ev, dom_child_handle(domain), &state->domain_sid, + &state->rids, &state->domain_name, &state->names); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_lookuprids_done, req); + return req; +} + +static void winbindd_lookuprids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_lookuprids_state *state = tevent_req_data( + req, struct winbindd_lookuprids_state); + NTSTATUS status, result; + + status = dcerpc_wbint_LookupRids_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_lookuprids_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_lookuprids_state *state = tevent_req_data( + req, struct winbindd_lookuprids_state); + NTSTATUS status; + char *result; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + DEBUG(5, ("Lookuprids failed: %s\n",nt_errstr(status))); + return status; + } + + result = talloc_strdup(response, ""); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<state->names.num_principals; i++) { + struct wbint_Principal *p = &state->names.principals[i]; + + result = talloc_asprintf_append_buffer( + result, "%d %s\n", (int)p->type, p->name); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + fstrcpy(response->data.domain_name, state->domain_name); + response->extra_data.data = result; + response->length += talloc_get_size(result); + return NT_STATUS_OK; +} + +static bool parse_ridlist(TALLOC_CTX *mem_ctx, char *ridstr, + uint32_t **prids, uint32_t *pnum_rids) +{ + uint32_t i, num_rids; + uint32_t *rids; + char *p; + + if (ridstr == NULL) { + return false; + } + + p = ridstr; + num_rids = 0; + + /* count rids */ + + while ((p = strchr(p, '\n')) != NULL) { + p += 1; + num_rids += 1; + } + + if (num_rids == 0) { + *pnum_rids = 0; + *prids = NULL; + return true; + } + + rids = talloc_array(mem_ctx, uint32_t, num_rids); + if (rids == NULL) { + return false; + } + + p = ridstr; + + for (i=0; i<num_rids; i++) { + char *q; + int error = 0; + + rids[i] = smb_strtoul(p, &q, 10, &error, SMB_STR_STANDARD); + if (error != 0 || *q != '\n') { + DEBUG(0, ("Got invalid ridstr: %s\n", p)); + return false; + } + p = q+1; + } + + *pnum_rids = num_rids; + *prids = rids; + return true; +} diff --git a/source3/winbindd/winbindd_lookupsid.c b/source3/winbindd/winbindd_lookupsid.c new file mode 100644 index 0000000..e20966b --- /dev/null +++ b/source3/winbindd/winbindd_lookupsid.c @@ -0,0 +1,104 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_LOOKUPSID + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "../libcli/security/security.h" +#include "lib/util/string_wrappers.h" + +struct winbindd_lookupsid_state { + struct dom_sid sid; + enum lsa_SidType type; + const char *domname; + const char *name; +}; + +static void winbindd_lookupsid_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_lookupsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_lookupsid_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_lookupsid_state); + if (req == NULL) { + return NULL; + } + + /* Ensure null termination */ + request->data.sid[sizeof(request->data.sid)-1]='\0'; + + DEBUG(3, ("lookupsid %s\n", request->data.sid)); + + if (!string_to_sid(&state->sid, request->data.sid)) { + DEBUG(5, ("%s not a SID\n", request->data.sid)); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = wb_lookupsid_send(state, ev, &state->sid); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_lookupsid_done, req); + return req; +} + +static void winbindd_lookupsid_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_lookupsid_state *state = tevent_req_data( + req, struct winbindd_lookupsid_state); + NTSTATUS status; + + status = wb_lookupsid_recv(subreq, state, &state->type, + &state->domname, &state->name); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_lookupsid_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_lookupsid_state *state = tevent_req_data( + req, struct winbindd_lookupsid_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + struct dom_sid_buf buf; + DEBUG(5, ("Could not lookup sid %s: %s\n", + dom_sid_str_buf(&state->sid, &buf), + nt_errstr(status))); + return status; + } + + fstrcpy(response->data.name.dom_name, state->domname); + fstrcpy(response->data.name.name, state->name); + response->data.name.type = state->type; + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_lookupsids.c b/source3/winbindd/winbindd_lookupsids.c new file mode 100644 index 0000000..a289fd8 --- /dev/null +++ b/source3/winbindd/winbindd_lookupsids.c @@ -0,0 +1,144 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_LOOKUPSIDS + Copyright (C) Volker Lendecke 2011 + + 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.h" +#include "../libcli/security/security.h" + +struct winbindd_lookupsids_state { + struct dom_sid *sids; + uint32_t num_sids; + struct lsa_RefDomainList *domains; + struct lsa_TransNameArray *names; +}; + +static void winbindd_lookupsids_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_lookupsids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_lookupsids_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_lookupsids_state); + if (req == NULL) { + return NULL; + } + + DEBUG(3, ("lookupsids\n")); + + if (request->extra_len == 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + if (request->extra_data.data[request->extra_len-1] != '\0') { + DEBUG(10, ("Got invalid sids list\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + if (!parse_sidlist(state, request->extra_data.data, + &state->sids, &state->num_sids)) { + DEBUG(10, ("parse_sidlist failed\n")); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + subreq = wb_lookupsids_send(state, ev, state->sids, state->num_sids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_lookupsids_done, req); + return req; +} + +static void winbindd_lookupsids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_lookupsids_state *state = tevent_req_data( + req, struct winbindd_lookupsids_state); + NTSTATUS status; + + status = wb_lookupsids_recv(subreq, state, &state->domains, + &state->names); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_lookupsids_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_lookupsids_state *state = tevent_req_data( + req, struct winbindd_lookupsids_state); + NTSTATUS status; + char *result; + uint32_t i; + + if (tevent_req_is_nterror(req, &status)) { + DEBUG(5, ("wb_lookupsids failed: %s\n", nt_errstr(status))); + return status; + } + + result = talloc_asprintf(response, "%d\n", (int)state->domains->count); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<state->domains->count; i++) { + struct dom_sid_buf sid_str; + + result = talloc_asprintf_append_buffer( + result, "%s %s\n", + dom_sid_str_buf(state->domains->domains[i].sid, + &sid_str), + state->domains->domains[i].name.string); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + result = talloc_asprintf_append_buffer( + result, "%d\n", (int)state->names->count); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<state->names->count; i++) { + struct lsa_TranslatedName *name; + + name = &state->names->names[i]; + + result = talloc_asprintf_append_buffer( + result, "%d %d %s\n", + (int)name->sid_index, (int)name->sid_type, + name->name.string); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + response->extra_data.data = result; + response->length += talloc_get_size(result); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_misc.c b/source3/winbindd/winbindd_misc.c new file mode 100644 index 0000000..3dbbc2f --- /dev/null +++ b/source3/winbindd/winbindd_misc.c @@ -0,0 +1,513 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - miscellaneous other functions + + Copyright (C) Tim Potter 2000 + Copyright (C) Andrew Bartlett 2002 + + 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.h" +#include "libcli/security/dom_sid.h" +#include "lib/util/string_wrappers.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +static char *get_trust_type_string(TALLOC_CTX *mem_ctx, + struct winbindd_tdc_domain *tdc, + struct winbindd_domain *domain) +{ + enum netr_SchannelType secure_channel_type = SEC_CHAN_NULL; + char *s = NULL; + + if (domain != NULL) { + secure_channel_type = domain->secure_channel_type; + } + + switch (secure_channel_type) { + case SEC_CHAN_NULL: { + if (domain == NULL) { + DBG_ERR("Missing domain [%s]\n", + tdc->domain_name); + return NULL; + } + if (domain->routing_domain == NULL) { + DBG_ERR("Missing routing for domain [%s]\n", + tdc->domain_name); + return NULL; + } + s = talloc_asprintf(mem_ctx, "Routed (via %s)", + domain->routing_domain->name); + if (s == NULL) { + return NULL; + } + break; + } + + case SEC_CHAN_LOCAL: + s = talloc_strdup(mem_ctx, "Local"); + if (s == NULL) { + return NULL; + } + break; + + case SEC_CHAN_WKSTA: + s = talloc_strdup(mem_ctx, "Workstation"); + if (s == NULL) { + return NULL; + } + break; + + case SEC_CHAN_BDC: { + int role = lp_server_role(); + + if (role == ROLE_DOMAIN_PDC || role == ROLE_IPA_DC) { + s = talloc_strdup(mem_ctx, "PDC"); + if (s == NULL) { + return NULL; + } + break; + } + + if (role == ROLE_DOMAIN_BDC) { + s = talloc_strdup(mem_ctx, "BDC"); + if (s == NULL) { + return NULL; + } + break; + } + + s = talloc_strdup(mem_ctx, "RWDC"); + if (s == NULL) { + return NULL; + } + break; + } + + case SEC_CHAN_RODC: + s = talloc_strdup(mem_ctx, "RODC"); + if (s == NULL) { + return NULL; + } + break; + + case SEC_CHAN_DNS_DOMAIN: + if (tdc->trust_attribs & LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN) { + s = talloc_strdup(mem_ctx, "External"); + if (s == NULL) { + return NULL; + } + break; + } + if (tdc->trust_attribs & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + s = talloc_strdup(mem_ctx, "In Forest"); + if (s == NULL) { + return NULL; + } + break; + } + if (tdc->trust_attribs & LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL) { + s = talloc_strdup(mem_ctx, "External"); + if (s == NULL) { + return NULL; + } + break; + } + if (tdc->trust_attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + s = talloc_strdup(mem_ctx, "Forest"); + if (s == NULL) { + return NULL; + } + break; + } + s = talloc_strdup(mem_ctx, "External"); + if (s == NULL) { + return NULL; + } + break; + + case SEC_CHAN_DOMAIN: + s = talloc_strdup(mem_ctx, "External"); + if (s == NULL) { + return NULL; + } + break; + + default: + DBG_ERR("Unhandled secure_channel_type %d for domain[%s]\n", + secure_channel_type, tdc->domain_name); + return NULL; + } + + return s; +} + +static bool trust_is_inbound(struct winbindd_tdc_domain *domain) +{ + if (domain->trust_flags & NETR_TRUST_FLAG_INBOUND) { + return true; + } + return false; +} + +static bool trust_is_outbound(struct winbindd_tdc_domain *domain) +{ + if (domain->trust_flags & NETR_TRUST_FLAG_OUTBOUND) { + return true; + } + return false; +} + +static bool trust_is_transitive(struct winbindd_tdc_domain *domain) +{ + bool transitive = false; + + /* + * Beware: order matters + */ + + if (domain->trust_attribs & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + transitive = true; + } + + if (domain->trust_attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + transitive = true; + } + + if (domain->trust_attribs & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) { + transitive = false; + } + + if (domain->trust_attribs & LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN) { + transitive = false; + } + + if (domain->trust_flags & NETR_TRUST_FLAG_PRIMARY) { + transitive = true; + } + + return transitive; +} + +bool winbindd_list_trusted_domains(struct winbindd_cli_state *state) +{ + struct winbindd_tdc_domain *dom_list = NULL; + size_t num_domains = 0; + int extra_data_len = 0; + char *extra_data = NULL; + size_t i = 0; + bool ret = false; + + DBG_NOTICE("[%s (%u)]: list trusted domains\n", + state->client_name, + (unsigned int)state->pid); + + if( !wcache_tdc_fetch_list( &dom_list, &num_domains )) { + goto done; + } + + extra_data = talloc_strdup(state->mem_ctx, ""); + if (extra_data == NULL) { + goto done; + } + + for ( i = 0; i < num_domains; i++ ) { + struct winbindd_domain *domain; + bool is_online = true; + struct winbindd_tdc_domain *d = NULL; + char *trust_type = NULL; + struct dom_sid_buf buf; + + d = &dom_list[i]; + domain = find_domain_from_name_noinit(d->domain_name); + if (domain) { + is_online = domain->online; + } + + trust_type = get_trust_type_string(talloc_tos(), d, domain); + if (trust_type == NULL) { + continue; + } + + extra_data = talloc_asprintf_append_buffer( + extra_data, + "%s\\%s\\%s\\%s\\%s\\%s\\%s\\%s\n", + d->domain_name, + d->dns_name ? d->dns_name : "", + dom_sid_str_buf(&d->sid, &buf), + trust_type, + trust_is_transitive(d) ? "Yes" : "No", + trust_is_inbound(d) ? "Yes" : "No", + trust_is_outbound(d) ? "Yes" : "No", + is_online ? "Online" : "Offline" ); + + TALLOC_FREE(trust_type); + } + + state->response->data.num_entries = num_domains; + + extra_data_len = strlen(extra_data); + if (extra_data_len > 0) { + + /* Strip the last \n */ + extra_data[extra_data_len-1] = '\0'; + + state->response->extra_data.data = extra_data; + state->response->length += extra_data_len; + } + + ret = true; +done: + TALLOC_FREE( dom_list ); + return ret; +} + +bool winbindd_dc_info(struct winbindd_cli_state *cli) +{ + struct winbindd_domain *domain; + char *dc_name, *dc_ip; + + cli->request->domain_name[sizeof(cli->request->domain_name)-1] = '\0'; + + DBG_NOTICE("[%s (%u)]: domain_info [%s]\n", + cli->client_name, + (unsigned int)cli->pid, + cli->request->domain_name); + + if (cli->request->domain_name[0] != '\0') { + domain = find_trust_from_name_noinit( + cli->request->domain_name); + if (domain == NULL) { + DEBUG(10, ("Could not find domain %s\n", + cli->request->domain_name)); + return false; + } + } else { + domain = find_our_domain(); + } + + if (!fetch_current_dc_from_gencache( + talloc_tos(), domain->name, &dc_name, &dc_ip)) { + DEBUG(10, ("fetch_current_dc_from_gencache(%s) failed\n", + domain->name)); + return false; + } + + cli->response->data.num_entries = 1; + cli->response->extra_data.data = talloc_asprintf( + cli->mem_ctx, "%s\n%s\n", dc_name, dc_ip); + + TALLOC_FREE(dc_name); + TALLOC_FREE(dc_ip); + + if (cli->response->extra_data.data == NULL) { + return false; + } + + /* must add one to length to copy the 0 for string termination */ + cli->response->length += + strlen((char *)cli->response->extra_data.data) + 1; + + return true; +} + +bool winbindd_ping(struct winbindd_cli_state *state) +{ + DBG_NOTICE("[%s (%u)]: ping\n", + state->client_name, + (unsigned int)state->pid); + return true; +} + +/* List various tidbits of information */ + +bool winbindd_info(struct winbindd_cli_state *state) +{ + + DBG_NOTICE("[%s (%u)]: request misc info\n", + state->client_name, + (unsigned int)state->pid); + + state->response->data.info.winbind_separator = *lp_winbind_separator(); + fstrcpy(state->response->data.info.samba_version, samba_version_string()); + return true; +} + +/* Tell the client the current interface version */ + +bool winbindd_interface_version(struct winbindd_cli_state *state) +{ + DBG_NOTICE("[%s (%u)]: request interface version (version = %d)\n", + state->client_name, + (unsigned int)state->pid, + WINBIND_INTERFACE_VERSION); + + state->response->data.interface_version = WINBIND_INTERFACE_VERSION; + return true; +} + +/* What domain are we a member of? */ + +bool winbindd_domain_name(struct winbindd_cli_state *state) +{ + DBG_NOTICE("[%s (%u)]: request domain name\n", + state->client_name, + (unsigned int)state->pid); + + fstrcpy(state->response->data.domain_name, lp_workgroup()); + return true; +} + +/* What's my name again? */ + +bool winbindd_netbios_name(struct winbindd_cli_state *state) +{ + DBG_NOTICE("[%s (%u)]: request netbios name\n", + state->client_name, + (unsigned int)state->pid); + + fstrcpy(state->response->data.netbios_name, lp_netbios_name()); + return true; +} + +/* Where can I find the privileged pipe? */ + +char *get_winbind_priv_pipe_dir(void) +{ + return state_path(talloc_tos(), WINBINDD_PRIV_SOCKET_SUBDIR); +} + +bool winbindd_priv_pipe_dir(struct winbindd_cli_state *state) +{ + char *priv_dir; + + DBG_NOTICE("[%s (%u)]: request location of privileged pipe\n", + state->client_name, + (unsigned int)state->pid); + + priv_dir = get_winbind_priv_pipe_dir(); + state->response->extra_data.data = talloc_move(state->mem_ctx, + &priv_dir); + + /* must add one to length to copy the 0 for string termination */ + state->response->length += + strlen((char *)state->response->extra_data.data) + 1; + + DBG_NOTICE("[%s (%u)]: response location of privileged pipe: %s\n", + state->client_name, + (unsigned int)state->pid, + priv_dir); + + return true; +} + +static void winbindd_setup_max_fds(void) +{ + int num_fds = MAX_OPEN_FUDGEFACTOR; + int actual_fds; + + num_fds += lp_winbind_max_clients(); + /* Add some more to account for 2 sockets open + when the client transitions from unprivileged + to privileged socket + */ + num_fds += lp_winbind_max_clients() / 10; + + /* Add one socket per child process + (yeah there are child processes other than the + domain children but only domain children can vary + with configuration + */ + num_fds += lp_winbind_max_domain_connections() * + (lp_allow_trusted_domains() ? WINBIND_MAX_DOMAINS_HINT : 1); + + actual_fds = set_maxfiles(num_fds); + + if (actual_fds < num_fds) { + DEBUG(1, ("winbindd_setup_max_fds: Information only: " + "requested %d open files, %d are available.\n", + num_fds, actual_fds)); + } +} + +bool winbindd_reload_services_file(const char *lfile) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + bool ret; + + if (lp_loaded()) { + char *fname = lp_next_configfile(talloc_tos(), lp_sub); + + if (file_exist(fname) && !strcsequal(fname,get_dyn_CONFIGFILE())) { + set_dyn_CONFIGFILE(fname); + } + TALLOC_FREE(fname); + } + + reopen_logs(); + ret = lp_load_global(get_dyn_CONFIGFILE()); + + /* if this is a child, restore the logfile to the special + name - <domain>, idmap, etc. */ + if (lfile && *lfile) { + lp_set_logfile(lfile); + } + + reopen_logs(); + load_interfaces(); + winbindd_setup_max_fds(); + + return(ret); +} + +static size_t *debug_call_depth = NULL; + +void winbind_debug_call_depth_setup(size_t *depth) +{ + debug_call_depth = depth; +} + +void winbind_call_flow(void *private_data, + enum tevent_thread_call_depth_cmd cmd, + struct tevent_req *req, + size_t depth, + const char *fname) +{ + switch (cmd) { + case TEVENT_CALL_FLOW_REQ_CREATE: + *debug_call_depth = depth; + DEBUG(20, ("flow: -> %s\n", fname)); + break; + case TEVENT_CALL_FLOW_REQ_NOTIFY_CB: + *debug_call_depth = depth; + DEBUG(20, ("flow: <- %s\n", fname)); + break; + case TEVENT_CALL_FLOW_REQ_QUEUE_TRIGGER: + *debug_call_depth = depth; + break; + case TEVENT_CALL_FLOW_REQ_RESET: + *debug_call_depth = depth; + break; + case TEVENT_CALL_FLOW_REQ_CANCEL: + case TEVENT_CALL_FLOW_REQ_CLEANUP: + case TEVENT_CALL_FLOW_REQ_QUEUE_ENTER: + case TEVENT_CALL_FLOW_REQ_QUEUE_LEAVE: + break; + } +} diff --git a/source3/winbindd/winbindd_msrpc.c b/source3/winbindd/winbindd_msrpc.c new file mode 100644 index 0000000..a7bd9be --- /dev/null +++ b/source3/winbindd/winbindd_msrpc.c @@ -0,0 +1,1124 @@ +/* + Unix SMB/CIFS implementation. + + Winbind rpc backend functions + + Copyright (C) Tim Potter 2000-2001,2003 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Volker Lendecke 2005 + Copyright (C) Guenther Deschner 2008 (pidl conversion) + + 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.h" +#include "winbindd_rpc.h" + +#include "../librpc/gen_ndr/ndr_samr_c.h" +#include "rpc_client/cli_pipe.h" +#include "rpc_client/cli_samr.h" +#include "rpc_client/cli_lsarpc.h" +#include "../libcli/security/security.h" +#include "libsmb/samlogon_cache.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +static NTSTATUS winbindd_lookup_names(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + uint32_t num_names, + const char **names, + const char ***domains, + struct dom_sid **sids, + enum lsa_SidType **types); + +/* Query display info for a domain. This returns enough information plus a + bit extra to give an overview of domain users for the User Manager + application. */ +static NTSTATUS msrpc_query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **prids) +{ + struct rpc_pipe_client *samr_pipe = NULL; + struct policy_handle dom_pol; + uint32_t *rids = NULL; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + + DEBUG(3, ("msrpc_query_user_list\n")); + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("query_user_list: No incoming trust for domain %s\n", + domain->name)); + status = NT_STATUS_OK; + goto done; + } + + status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_query_user_list(tmp_ctx, + samr_pipe, + &dom_pol, + &domain->sid, + &rids); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (prids) { + *prids = talloc_move(mem_ctx, &rids); + } + +done: + TALLOC_FREE(rids); + TALLOC_FREE(tmp_ctx); + return status; +} + +/* list all domain groups */ +static NTSTATUS msrpc_enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *pnum_info, + struct wb_acct_info **pinfo) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol; + struct wb_acct_info *info = NULL; + uint32_t num_info = 0; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + + DEBUG(3,("msrpc_enum_dom_groups\n")); + + if (pnum_info) { + *pnum_info = 0; + } + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("enum_domain_groups: No incoming trust for domain %s\n", + domain->name)); + status = NT_STATUS_OK; + goto done; + } + + status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_enum_dom_groups(tmp_ctx, + samr_pipe, + &dom_pol, + &num_info, + &info); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (pnum_info) { + *pnum_info = num_info; + } + + if (pinfo) { + *pinfo = talloc_move(mem_ctx, &info); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* List all domain groups */ + +static NTSTATUS msrpc_enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *pnum_info, + struct wb_acct_info **pinfo) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol; + struct wb_acct_info *info = NULL; + uint32_t num_info = 0; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + + DEBUG(3,("msrpc_enum_local_groups\n")); + + if (pnum_info) { + *pnum_info = 0; + } + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("enum_local_groups: No incoming trust for domain %s\n", + domain->name)); + status = NT_STATUS_OK; + goto done; + } + + status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_enum_local_groups(mem_ctx, + samr_pipe, + &dom_pol, + &num_info, + &info); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (pnum_info) { + *pnum_info = num_info; + } + + if (pinfo) { + *pinfo = talloc_move(mem_ctx, &info); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* convert a single name to a sid in a domain */ +static NTSTATUS msrpc_name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + uint32_t flags, + const char **pdom_name, + struct dom_sid *sid, + enum lsa_SidType *type) +{ + NTSTATUS result; + struct dom_sid *sids = NULL; + enum lsa_SidType *types = NULL; + char *full_name = NULL; + const char *names[1]; + const char **domains; + 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); + } else if (domain_name == NULL || *domain_name == '\0') { + full_name = talloc_asprintf(mem_ctx, "%s", name); + } else { + full_name = talloc_asprintf(mem_ctx, "%s\\%s", domain_name, name); + } + if (!full_name) { + DEBUG(0, ("talloc_asprintf failed!\n")); + return NT_STATUS_NO_MEMORY; + } + + DEBUG(3, ("msrpc_name_to_sid: name=%s\n", full_name)); + + name_map_status = normalize_name_unmap(mem_ctx, full_name, + &mapped_name); + + /* Reset the full_name pointer if we mapped anything */ + + 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 )); + + names[0] = full_name; + + result = winbindd_lookup_names(mem_ctx, domain, 1, + names, &domains, + &sids, &types); + if (!NT_STATUS_IS_OK(result)) + return result; + + /* Return rid and type if lookup successful */ + + if (pdom_name != NULL) { + const char *dom_name; + + dom_name = talloc_strdup(mem_ctx, domains[0]); + if (dom_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *pdom_name = dom_name; + } + + sid_copy(sid, &sids[0]); + *type = types[0]; + + return NT_STATUS_OK; +} + +/* + convert a domain SID to a user or group name +*/ +static NTSTATUS msrpc_sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + char **domain_name, + char **name, + enum lsa_SidType *type) +{ + char **domains; + char **names; + enum lsa_SidType *types = NULL; + NTSTATUS result; + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; + char *mapped_name = NULL; + struct dom_sid_buf buf; + + DEBUG(3, ("msrpc_sid_to_name: %s for domain %s\n", + dom_sid_str_buf(sid, &buf), + domain->name)); + + result = winbindd_lookup_sids(mem_ctx, + domain, + 1, + sid, + &domains, + &names, + &types); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(2,("msrpc_sid_to_name: failed to lookup sids: %s\n", + nt_errstr(result))); + return result; + } + + + *type = (enum lsa_SidType)types[0]; + *domain_name = domains[0]; + *name = names[0]; + + DEBUG(5,("Mapped sid to [%s]\\[%s]\n", domains[0], *name)); + + name_map_status = normalize_name_map(mem_ctx, domain->name, *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; +} + +static NTSTATUS msrpc_rids_to_names(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + uint32_t *rids, + size_t num_rids, + char **domain_name, + char ***names, + enum lsa_SidType **types) +{ + char **domains; + NTSTATUS result; + struct dom_sid *sids; + size_t i; + char **ret_names; + + DEBUG(3, ("msrpc_rids_to_names: domain %s\n", domain->name )); + + if (num_rids) { + sids = talloc_array(mem_ctx, struct dom_sid, num_rids); + if (sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + } else { + sids = NULL; + } + + for (i=0; i<num_rids; i++) { + if (!sid_compose(&sids[i], sid, rids[i])) { + return NT_STATUS_INTERNAL_ERROR; + } + } + + result = winbindd_lookup_sids(mem_ctx, + domain, + num_rids, + sids, + &domains, + names, + types); + + if (!NT_STATUS_IS_OK(result) && + !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) { + return result; + } + + 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) { + name_map_status = normalize_name_map(mem_ctx, + domain->name, + 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]; + } + } + + return result; +} + +/* Lookup groups a user is a member of. I wish Unix had a call like this! */ +static NTSTATUS msrpc_lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *pnum_groups, + struct dom_sid **puser_grpsids) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol; + struct dom_sid *user_grpsids = NULL; + struct dom_sid_buf buf; + uint32_t num_groups = 0; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + + DEBUG(3,("msrpc_lookup_usergroups sid=%s\n", + dom_sid_str_buf(user_sid, &buf))); + + *pnum_groups = 0; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Check if we have a cached user_info_3 */ + status = lookup_usergroups_cached(tmp_ctx, + user_sid, + &num_groups, + &user_grpsids); + if (NT_STATUS_IS_OK(status)) { + goto cached; + } + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("lookup_usergroups: No incoming trust for domain %s\n", + domain->name)); + + /* Tell the cache manager not to remember this one */ + status = NT_STATUS_SYNCHRONIZATION_REQUIRED; + goto done; + } + + /* no cache; hit the wire */ + status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_lookup_usergroups(tmp_ctx, + samr_pipe, + &dom_pol, + &domain->sid, + user_sid, + &num_groups, + &user_grpsids); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + +cached: + *pnum_groups = num_groups; + + if (puser_grpsids) { + *puser_grpsids = talloc_move(mem_ctx, &user_grpsids); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; + return NT_STATUS_OK; +} + +#define MAX_SAM_ENTRIES_W2K 0x400 /* 1024 */ + +static NTSTATUS msrpc_lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, const struct dom_sid *sids, + uint32_t *pnum_aliases, + uint32_t **palias_rids) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol; + uint32_t num_aliases = 0; + uint32_t *alias_rids = NULL; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + + DEBUG(3,("msrpc_lookup_useraliases\n")); + + if (pnum_aliases) { + *pnum_aliases = 0; + } + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (!winbindd_can_contact_domain(domain)) { + DEBUG(10,("msrpc_lookup_useraliases: No incoming trust for domain %s\n", + domain->name)); + /* Tell the cache manager not to remember this one */ + status = NT_STATUS_SYNCHRONIZATION_REQUIRED; + goto done; + } + + status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_lookup_useraliases(tmp_ctx, + samr_pipe, + &dom_pol, + num_sids, + sids, + &num_aliases, + &alias_rids); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (pnum_aliases) { + *pnum_aliases = num_aliases; + } + + if (palias_rids) { + *palias_rids = talloc_move(mem_ctx, &alias_rids); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* lookup alias membership */ +static NTSTATUS msrpc_lookup_aliasmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *alias_sid, + enum lsa_SidType type, + uint32_t *pnum_sids, + struct dom_sid **sid_mem) +{ + struct rpc_pipe_client *samr_pipe = NULL; + struct policy_handle dom_pol; + struct dom_sid *alias_members = NULL; + struct dom_sid_buf buf; + uint32_t num_groups = 0; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + + D_INFO("Lookup alias members in domain=%s for sid=%s.\n", + domain->name, + dom_sid_str_buf(alias_sid, &buf)); + + *pnum_sids = 0; + + if (!winbindd_can_contact_domain(domain)) { + D_DEBUG("No incoming trust for domain %s\n", domain->name); + status = NT_STATUS_OK; + goto done; + } + + status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_lookup_aliasmem(tmp_ctx, + samr_pipe, + &dom_pol, + &domain->sid, + alias_sid, + type, + &num_groups, + &alias_members); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + *pnum_sids = num_groups; + if (sid_mem) { + *sid_mem = talloc_move(mem_ctx, &alias_members); + } + +done: + talloc_free(tmp_ctx); + return status; +} + +/* Lookup group membership given a rid. */ +static NTSTATUS msrpc_lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem, + char ***names, + uint32_t **name_types) +{ + NTSTATUS status, result; + uint32_t i, total_names = 0; + struct policy_handle dom_pol, group_pol; + uint32_t des_access = SEC_FLAG_MAXIMUM_ALLOWED; + uint32_t *rid_mem = NULL; + uint32_t group_rid; + unsigned int j, r; + struct rpc_pipe_client *cli; + unsigned int orig_timeout; + struct samr_RidAttrArray *rids = NULL; + struct dcerpc_binding_handle *b; + struct dom_sid_buf buf; + + DEBUG(3,("msrpc_lookup_groupmem: %s sid=%s\n", domain->name, + dom_sid_str_buf(group_sid, &buf))); + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("lookup_groupmem: No incoming trust for domain %s\n", + domain->name)); + return NT_STATUS_OK; + } + + if (!sid_peek_check_rid(&domain->sid, group_sid, &group_rid)) + return NT_STATUS_UNSUCCESSFUL; + + *num_names = 0; + + result = cm_connect_sam(domain, mem_ctx, false, &cli, &dom_pol); + if (!NT_STATUS_IS_OK(result)) + return result; + + b = cli->binding_handle; + + status = dcerpc_samr_OpenGroup(b, mem_ctx, + &dom_pol, + des_access, + group_rid, + &group_pol, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + return status; + } + + /* Step #1: Get a list of user rids that are the members of the + group. */ + + /* This call can take a long time - allow the server to time out. + 35 seconds should do it. */ + + orig_timeout = rpccli_set_timeout(cli, 35000); + + status = dcerpc_samr_QueryGroupMember(b, mem_ctx, + &group_pol, + &rids, + &result); + + /* And restore our original timeout. */ + rpccli_set_timeout(cli, orig_timeout); + + { + NTSTATUS _result; + dcerpc_samr_Close(b, mem_ctx, &group_pol, &_result); + } + + if (any_nt_status_not_ok(status, result, &status)) { + return status; + } + + if (!rids || !rids->count) { + names = NULL; + name_types = NULL; + sid_mem = NULL; + return NT_STATUS_OK; + } + + *num_names = rids->count; + rid_mem = rids->rids; + + /* Step #2: Convert list of rids into list of usernames. Do this + in bunches of ~1000 to avoid crashing NT4. It looks like there + is a buffer overflow or something like that lurking around + somewhere. */ + +#define MAX_LOOKUP_RIDS 900 + + *names = talloc_zero_array(mem_ctx, char *, *num_names); + *name_types = talloc_zero_array(mem_ctx, uint32_t, *num_names); + *sid_mem = talloc_zero_array(mem_ctx, struct dom_sid, *num_names); + + for (j=0;j<(*num_names);j++) + sid_compose(&(*sid_mem)[j], &domain->sid, rid_mem[j]); + + if (*num_names>0 && (!*names || !*name_types)) + return NT_STATUS_NO_MEMORY; + + for (i = 0; i < *num_names; i += MAX_LOOKUP_RIDS) { + int num_lookup_rids = MIN(*num_names - i, MAX_LOOKUP_RIDS); + struct lsa_Strings tmp_names; + struct samr_Ids tmp_types; + + /* Lookup a chunk of rids */ + + status = dcerpc_samr_LookupRids(b, mem_ctx, + &dom_pol, + num_lookup_rids, + &rid_mem[i], + &tmp_names, + &tmp_types, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* see if we have a real error (and yes the + STATUS_SOME_UNMAPPED is the one returned from 2k) */ + + if (!NT_STATUS_IS_OK(result) && + !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) + return result; + + /* Copy result into array. The talloc system will take + care of freeing the temporary arrays later on. */ + + if (tmp_names.count != num_lookup_rids) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + if (tmp_types.count != num_lookup_rids) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + for (r=0; r<tmp_names.count; r++) { + if (tmp_types.ids[r] == SID_NAME_UNKNOWN) { + continue; + } + if (total_names >= *num_names) { + break; + } + (*names)[total_names] = fill_domain_username_talloc( + mem_ctx, domain->name, + tmp_names.names[r].string, true); + (*name_types)[total_names] = tmp_types.ids[r]; + total_names += 1; + } + } + + *num_names = total_names; + + return NT_STATUS_OK; +} + +/* get a list of trusted domains */ +static NTSTATUS msrpc_trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *ptrust_list) +{ + struct rpc_pipe_client *lsa_pipe; + struct policy_handle lsa_policy; + struct netr_DomainTrust *trusts = NULL; + uint32_t num_trusts = 0; + TALLOC_CTX *tmp_ctx; + NTSTATUS status; + + DEBUG(3,("msrpc_trusted_domains\n")); + + if (ptrust_list) { + ZERO_STRUCTP(ptrust_list); + } + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = cm_connect_lsa(domain, tmp_ctx, &lsa_pipe, &lsa_policy); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_trusted_domains(tmp_ctx, + lsa_pipe, + &lsa_policy, + &num_trusts, + &trusts); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (ptrust_list) { + ptrust_list->count = num_trusts; + ptrust_list->array = talloc_move(mem_ctx, &trusts); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* find the lockout policy for a domain */ +static NTSTATUS msrpc_lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *lockout_policy) +{ + NTSTATUS status, result; + struct rpc_pipe_client *cli; + struct policy_handle dom_pol; + union samr_DomainInfo *info = NULL; + struct dcerpc_binding_handle *b; + + DEBUG(3, ("msrpc_lockout_policy: fetch lockout policy for %s\n", domain->name)); + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("msrpc_lockout_policy: No incoming trust for domain %s\n", + domain->name)); + return NT_STATUS_NOT_SUPPORTED; + } + + status = cm_connect_sam(domain, mem_ctx, false, &cli, &dom_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + b = cli->binding_handle; + + status = dcerpc_samr_QueryDomainInfo(b, mem_ctx, + &dom_pol, + DomainLockoutInformation, + &info, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + return status; + } + + *lockout_policy = info->info12; + + DEBUG(10,("msrpc_lockout_policy: lockout_threshold %d\n", + info->info12.lockout_threshold)); + + done: + + return status; +} + +/* find the password policy for a domain */ +static NTSTATUS msrpc_password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *password_policy) +{ + NTSTATUS status, result; + struct rpc_pipe_client *cli; + struct policy_handle dom_pol; + union samr_DomainInfo *info = NULL; + struct dcerpc_binding_handle *b; + + DEBUG(3, ("msrpc_password_policy: fetch password policy for %s\n", + domain->name)); + + if ( !winbindd_can_contact_domain( domain ) ) { + DEBUG(10,("msrpc_password_policy: No incoming trust for domain %s\n", + domain->name)); + return NT_STATUS_NOT_SUPPORTED; + } + + status = cm_connect_sam(domain, mem_ctx, false, &cli, &dom_pol); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + b = cli->binding_handle; + + status = dcerpc_samr_QueryDomainInfo(b, mem_ctx, + &dom_pol, + DomainPasswordInformation, + &info, + &result); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + *password_policy = info->info1; + + DEBUG(10,("msrpc_password_policy: min_length_password %d\n", + info->info1.min_password_length)); + + done: + + return status; +} + +static enum lsa_LookupNamesLevel winbindd_lookup_level( + struct winbindd_domain *domain) +{ + enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_DOMAINS_ONLY; + + if (domain->internal) { + level = LSA_LOOKUP_NAMES_ALL; + } else if (domain->secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + if (domain->domain_flags & NETR_TRUST_FLAG_IN_FOREST) { + /* + * TODO: + * + * Depending on what we want to resolve. We need to use: + * 1. LsapLookupXForestReferral(5)/LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY + * if we want to pass the request into the direction of the forest + * root domain. The forest root domain uses + * LsapLookupXForestResolve(6)/LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2 + * when passing the request to trusted forests. + * 2. LsapLookupGC(4)/LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY + * if we're not a GC and want to resolve a name within our own forest. + * + * As we don't support more than one domain in our own forest + * and always try to be a GC for now, we just set + * LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY. + */ + level = LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY; + } else if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + /* + * This is LsapLookupXForestResolve(6)/LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2 + */ + level = LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2; + } else { + /* + * This is LsapLookupTDL(3)/LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY + */ + level = LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY; + } + } else if (domain->secure_channel_type == SEC_CHAN_DOMAIN) { + /* + * This is LsapLookupTDL(3)/LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY + */ + level = LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY; + } else if (domain->rodc) { + level = LSA_LOOKUP_NAMES_RODC_REFERRAL_TO_FULL_DC; + } else { + /* + * This is LsapLookupPDC(2)/LSA_LOOKUP_NAMES_DOMAINS_ONLY + */ + level = LSA_LOOKUP_NAMES_DOMAINS_ONLY; + } + + return level; +} + +NTSTATUS winbindd_lookup_sids(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + uint32_t num_sids, + const struct dom_sid *sids, + char ***domains, + char ***names, + enum lsa_SidType **types) +{ + NTSTATUS status; + NTSTATUS result; + struct rpc_pipe_client *cli = NULL; + struct dcerpc_binding_handle *b = NULL; + struct policy_handle lsa_policy; + unsigned int orig_timeout; + bool use_lookupsids3 = false; + bool retried = false; + enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_ALL; + + connect: + status = cm_connect_lsat(domain, mem_ctx, &cli, &lsa_policy); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + b = cli->binding_handle; + + if (cli->transport->transport == NCACN_IP_TCP) { + use_lookupsids3 = true; + } + + level = winbindd_lookup_level(domain); + + /* + * This call can take a long time + * allow the server to time out. + * 35 seconds should do it. + */ + orig_timeout = dcerpc_binding_handle_set_timeout(b, 35000); + + status = dcerpc_lsa_lookup_sids_generic(b, + mem_ctx, + &lsa_policy, + num_sids, + sids, + level, + domains, + names, + types, + use_lookupsids3, + &result); + + /* And restore our original timeout. */ + dcerpc_binding_handle_set_timeout(b, orig_timeout); + + if (reset_cm_connection_on_error(domain, b, status)) { + /* + * This can happen if the schannel key is not + * valid anymore, we need to invalidate the + * all connections to the dc and reestablish + * a netlogon connection first. + */ + domain->can_do_ncacn_ip_tcp = domain->active_directory; + if (!retried) { + retried = true; + goto connect; + } + status = NT_STATUS_ACCESS_DENIED; + } + + if (any_nt_status_not_ok(status, result, &status)) { + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS winbindd_lookup_names(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + uint32_t num_names, + const char **names, + const char ***domains, + struct dom_sid **sids, + enum lsa_SidType **types) +{ + NTSTATUS status; + NTSTATUS result; + struct rpc_pipe_client *cli = NULL; + struct dcerpc_binding_handle *b = NULL; + struct policy_handle lsa_policy; + unsigned int orig_timeout = 0; + bool use_lookupnames4 = false; + bool retried = false; + enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_ALL; + + connect: + status = cm_connect_lsat(domain, mem_ctx, &cli, &lsa_policy); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + b = cli->binding_handle; + + if (cli->transport->transport == NCACN_IP_TCP) { + use_lookupnames4 = true; + } + + level = winbindd_lookup_level(domain); + + /* + * This call can take a long time + * allow the server to time out. + * 35 seconds should do it. + */ + orig_timeout = dcerpc_binding_handle_set_timeout(b, 35000); + + status = dcerpc_lsa_lookup_names_generic(b, + mem_ctx, + &lsa_policy, + num_names, + (const char **) names, + domains, + level, + sids, + types, + use_lookupnames4, + &result); + + /* And restore our original timeout. */ + dcerpc_binding_handle_set_timeout(b, orig_timeout); + + if (reset_cm_connection_on_error(domain, b, status)) { + /* + * This can happen if the schannel key is not + * valid anymore, we need to invalidate the + * all connections to the dc and reestablish + * a netlogon connection first. + */ + if (!retried) { + retried = true; + goto connect; + } + status = NT_STATUS_ACCESS_DENIED; + } + + if (any_nt_status_not_ok(status, result, &status)) { + return status; + } + + return NT_STATUS_OK; +} + +/* the rpc backend methods are exposed via this structure */ +struct winbindd_methods msrpc_methods = { + False, + msrpc_query_user_list, + msrpc_enum_dom_groups, + msrpc_enum_local_groups, + msrpc_name_to_sid, + msrpc_sid_to_name, + msrpc_rids_to_names, + msrpc_lookup_usergroups, + msrpc_lookup_useraliases, + msrpc_lookup_groupmem, + msrpc_lookup_aliasmem, + msrpc_lockout_policy, + msrpc_password_policy, + msrpc_trusted_domains, +}; diff --git a/source3/winbindd/winbindd_ndr.c b/source3/winbindd/winbindd_ndr.c new file mode 100644 index 0000000..a52a704 --- /dev/null +++ b/source3/winbindd/winbindd_ndr.c @@ -0,0 +1,162 @@ +/* + * Unix SMB/CIFS implementation. + * winbindd debug helper + * Copyright (C) Guenther Deschner 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.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include "../librpc/gen_ndr/ndr_security.h" +#include "../librpc/gen_ndr/ndr_lsa.h" +#include "../librpc/ndr/libndr.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/**************************************************************** +****************************************************************/ + +void ndr_print_winbindd_child(struct ndr_print *ndr, + const char *name, + const struct winbindd_child *r) +{ + ndr_print_struct(ndr, name, "winbindd_child"); + ndr->depth++; + ndr_print_uint32(ndr, "pid", (uint32_t)r->pid); +#if 0 + ndr_print_winbindd_domain(ndr, "domain", r->domain); +#else + ndr_print_ptr(ndr, "domain", r->domain); +#endif + ndr_print_string(ndr, "logfilename", r->logfilename); + /* struct fd_event event; */ + ndr_print_ptr(ndr, "lockout_policy_event", r->lockout_policy_event); + ndr->depth--; +} + +/**************************************************************** +****************************************************************/ + +void ndr_print_winbindd_cm_conn(struct ndr_print *ndr, + const char *name, + const struct winbindd_cm_conn *r) +{ + ndr_print_struct(ndr, name, "winbindd_cm_conn"); + ndr->depth++; + ndr_print_ptr(ndr, "cli", r->cli); + ndr_print_ptr(ndr, "samr_pipe", r->samr_pipe); + ndr_print_policy_handle(ndr, "sam_connect_handle", &r->sam_connect_handle); + ndr_print_policy_handle(ndr, "sam_domain_handle", &r->sam_domain_handle); + ndr_print_ptr(ndr, "lsa_pipe", r->lsa_pipe); + ndr_print_policy_handle(ndr, "lsa_policy", &r->lsa_policy); + ndr_print_ptr(ndr, "netlogon_pipe", r->netlogon_pipe); + ndr->depth--; +} + +/**************************************************************** +****************************************************************/ + +#ifdef HAVE_ADS +extern struct winbindd_methods ads_methods; +extern struct winbindd_methods reconnect_ads_methods; +#endif +extern struct winbindd_methods msrpc_methods; +extern struct winbindd_methods builtin_passdb_methods; +extern struct winbindd_methods sam_passdb_methods; +extern struct winbindd_methods reconnect_methods; + +void ndr_print_winbindd_methods(struct ndr_print *ndr, + const char *name, + const struct winbindd_methods *r) +{ + ndr_print_struct(ndr, name, "winbindd_methods"); + ndr->depth++; + + if (r == NULL) { + ndr_print_string(ndr, name, "(NULL)"); + ndr->depth--; + return; + } + + if (r == &msrpc_methods) { + ndr_print_string(ndr, name, "msrpc_methods"); +#ifdef HAVE_ADS + } else if (r == &ads_methods) { + ndr_print_string(ndr, name, "ads_methods"); + } else if (r == &reconnect_ads_methods) { + ndr_print_string(ndr, name, "reconnect_ads_methods"); +#endif + } else if (r == &builtin_passdb_methods) { + ndr_print_string(ndr, name, "builtin_passdb_methods"); + } else if (r == &sam_passdb_methods) { + ndr_print_string(ndr, name, "sam_passdb_methods"); + } else if (r == &reconnect_methods) { + ndr_print_string(ndr, name, "reconnect_methods"); + } else { + ndr_print_string(ndr, name, "UNKNOWN"); + } + ndr->depth--; +} + +/**************************************************************** +****************************************************************/ + +void ndr_print_winbindd_domain(struct ndr_print *ndr, + const char *name, + const struct winbindd_domain *r) +{ + int i; + if (!r) { + return; + } + + ndr_print_struct(ndr, name, "winbindd_domain"); + ndr->depth++; + ndr_print_string(ndr, "name", r->name); + ndr_print_string(ndr, "alt_name", r->alt_name); + ndr_print_string(ndr, "forest_name", r->forest_name); + ndr_print_dom_sid(ndr, "sid", &r->sid); + ndr_print_netr_TrustFlags(ndr, "domain_flags", r->domain_flags); + ndr_print_lsa_TrustType(ndr, "domain_type", r->domain_type); + ndr_print_lsa_TrustAttributes(ndr, "domain_trust_attribs", r->domain_trust_attribs); + ndr_print_bool(ndr, "initialized", r->initialized); + ndr_print_bool(ndr, "native_mode", r->native_mode); + ndr_print_bool(ndr, "active_directory", r->active_directory); + ndr_print_bool(ndr, "primary", r->primary); + ndr_print_bool(ndr, "internal", r->internal); + ndr_print_bool(ndr, "online", r->online); + ndr_print_time_t(ndr, "startup_time", r->startup_time); + ndr_print_bool(ndr, "startup", r->startup); + ndr_print_winbindd_methods(ndr, "backend", r->backend); + ndr_print_ptr(ndr, + "backend_data.samr_pipes", + r->backend_data.samr_pipes); + ndr_print_ptr(ndr, + "backend_data.ads_conn", + r->backend_data.ads_conn); + ndr_print_string(ndr, "dcname", r->dcname); + ndr_print_sockaddr_storage(ndr, "dcaddr", &r->dcaddr); + ndr_print_time_t(ndr, "last_seq_check", r->last_seq_check); + ndr_print_uint32(ndr, "sequence_number", r->sequence_number); + ndr_print_NTSTATUS(ndr, "last_status", r->last_status); + ndr_print_winbindd_cm_conn(ndr, "conn", &r->conn); + for (i=0; i<talloc_array_length(r->children); i++) { + ndr_print_winbindd_child(ndr, "children", &r->children[i]); + } + ndr_print_ptr(ndr, "check_online_event", r->check_online_event); + ndr->depth--; +} diff --git a/source3/winbindd/winbindd_pam.c b/source3/winbindd/winbindd_pam.c new file mode 100644 index 0000000..6c890c8 --- /dev/null +++ b/source3/winbindd/winbindd_pam.c @@ -0,0 +1,3616 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - pam auth functions + + Copyright (C) Andrew Tridgell 2000 + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett 2001-2002 + Copyright (C) Guenther Deschner 2005 + + 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 "ntdomain.h" +#include "winbindd.h" +#include "libsmb/namequery.h" +#include "../libcli/auth/libcli_auth.h" +#include "libcli/auth/pam_errors.h" +#include "../librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/rpc/dcesrv_core.h" +#include "librpc/gen_ndr/ndr_winbind.h" +#include "rpc_client/cli_pipe.h" +#include "rpc_client/cli_samr.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include "rpc_client/cli_netlogon.h" +#include "smb_krb5.h" +#include "../libcli/security/security.h" +#include "ads.h" +#include "../librpc/gen_ndr/krb5pac.h" +#include "passdb/machine_sid.h" +#include "auth.h" +#include "../lib/tsocket/tsocket.h" +#include "auth/kerberos/pac_utils.h" +#include "auth/gensec/gensec.h" +#include "librpc/crypto/gse_krb5.h" +#include "lib/afs/afs_funcs.h" +#include "libsmb/samlogon_cache.h" +#include "rpc_client/util_netlogon.h" +#include "param/param.h" +#include "messaging/messaging.h" +#include "lib/util/string_wrappers.h" +#include "lib/crypto/gnutls_helpers.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/crypto.h> +#include "lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +#define LOGON_KRB5_FAIL_CLOCK_SKEW 0x02000000 + +static NTSTATUS append_info3_as_txt(TALLOC_CTX *mem_ctx, + struct winbindd_response *resp, + uint16_t validation_level, + union netr_Validation *validation) +{ + struct netr_SamInfo3 *info3 = NULL; + char *ex = NULL; + uint32_t i; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *frame = talloc_stackframe(); + + status = map_validation_to_info3(frame, + validation_level, + validation, + &info3); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + + resp->data.auth.info3.logon_time = + nt_time_to_unix(info3->base.logon_time); + resp->data.auth.info3.logoff_time = + nt_time_to_unix(info3->base.logoff_time); + resp->data.auth.info3.kickoff_time = + nt_time_to_unix(info3->base.kickoff_time); + resp->data.auth.info3.pass_last_set_time = + nt_time_to_unix(info3->base.last_password_change); + resp->data.auth.info3.pass_can_change_time = + nt_time_to_unix(info3->base.allow_password_change); + resp->data.auth.info3.pass_must_change_time = + nt_time_to_unix(info3->base.force_password_change); + + resp->data.auth.info3.logon_count = info3->base.logon_count; + resp->data.auth.info3.bad_pw_count = info3->base.bad_password_count; + + resp->data.auth.info3.user_rid = info3->base.rid; + resp->data.auth.info3.group_rid = info3->base.primary_gid; + sid_to_fstring(resp->data.auth.info3.dom_sid, info3->base.domain_sid); + + resp->data.auth.info3.num_groups = info3->base.groups.count; + resp->data.auth.info3.user_flgs = info3->base.user_flags; + + resp->data.auth.info3.acct_flags = info3->base.acct_flags; + resp->data.auth.info3.num_other_sids = info3->sidcount; + + fstrcpy(resp->data.auth.info3.user_name, + info3->base.account_name.string); + fstrcpy(resp->data.auth.info3.full_name, + info3->base.full_name.string); + fstrcpy(resp->data.auth.info3.logon_script, + info3->base.logon_script.string); + fstrcpy(resp->data.auth.info3.profile_path, + info3->base.profile_path.string); + fstrcpy(resp->data.auth.info3.home_dir, + info3->base.home_directory.string); + fstrcpy(resp->data.auth.info3.dir_drive, + info3->base.home_drive.string); + + fstrcpy(resp->data.auth.info3.logon_srv, + info3->base.logon_server.string); + fstrcpy(resp->data.auth.info3.logon_dom, + info3->base.logon_domain.string); + + resp->data.auth.validation_level = validation_level; + if (validation_level == 6) { + fstrcpy(resp->data.auth.info6.dns_domainname, + validation->sam6->dns_domainname.string); + fstrcpy(resp->data.auth.info6.principal_name, + validation->sam6->principal_name.string); + } + + ex = talloc_strdup(frame, ""); + if (ex == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + for (i=0; i < info3->base.groups.count; i++) { + ex = talloc_asprintf_append_buffer(ex, "0x%08X:0x%08X\n", + info3->base.groups.rids[i].rid, + info3->base.groups.rids[i].attributes); + if (ex == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + + for (i=0; i < info3->sidcount; i++) { + struct dom_sid_buf sidbuf; + + ex = talloc_asprintf_append_buffer( + ex, + "%s:0x%08X\n", + dom_sid_str_buf(info3->sids[i].sid, &sidbuf), + info3->sids[i].attributes); + if (ex == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + } + + resp->length += talloc_get_size(ex); + resp->extra_data.data = talloc_move(mem_ctx, &ex); + + status = NT_STATUS_OK; +out: + TALLOC_FREE(frame); + return status; +} + +static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx, + struct winbindd_response *resp, + struct netr_SamInfo3 *info3) +{ + DATA_BLOB blob; + enum ndr_err_code ndr_err; + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, 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")); + return ndr_map_error2ntstatus(ndr_err); + } + + resp->extra_data.data = blob.data; + resp->length += blob.length; + + return NT_STATUS_OK; +} + +static NTSTATUS append_unix_username(uint16_t validation_level, + union netr_Validation *validation, + const char *name_domain, + const char *name_user, + TALLOC_CTX *mem_ctx, + char **_unix_username) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *nt_username = NULL; + const char *nt_domain = NULL; + char *unix_username = NULL; + struct netr_SamBaseInfo *base_info = NULL; + NTSTATUS status; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* We've been asked to return the unix username, per + 'winbind use default domain' settings and the like */ + + switch (validation_level) { + case 3: + base_info = &validation->sam3->base; + break; + case 6: + base_info = &validation->sam6->base; + break; + default: + DBG_ERR("Invalid validation level %d\n", validation_level); + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + nt_domain = talloc_strdup(tmp_ctx, base_info->logon_domain.string); + if (!nt_domain) { + /* If the server didn't give us one, just use the one + * we sent them */ + nt_domain = name_domain; + } + + nt_username = talloc_strdup(tmp_ctx, base_info->account_name.string); + if (!nt_username) { + /* If the server didn't give us one, just use the one + * we sent them */ + nt_username = name_user; + } + + unix_username = fill_domain_username_talloc(tmp_ctx, + nt_domain, + nt_username, + true); + if (unix_username == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + DBG_INFO("Setting unix username to [%s]\n", unix_username); + + *_unix_username = talloc_move(mem_ctx, &unix_username); + + status = NT_STATUS_OK; +out: + TALLOC_FREE(tmp_ctx); + + return status; +} + +static NTSTATUS append_afs_token(uint16_t validation_level, + union netr_Validation *validation, + const char *name_domain, + const char *name_user, + TALLOC_CTX *mem_ctx, + DATA_BLOB *_blob) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *afsname = NULL; + char *cell; + char *token; + struct netr_SamBaseInfo *base_info = NULL; + NTSTATUS status; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + switch (validation_level) { + case 3: + base_info = &validation->sam3->base; + break; + case 6: + base_info = &validation->sam6->base; + break; + default: + DBG_ERR("Invalid validation level %d\n", validation_level); + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + afsname = talloc_strdup(tmp_ctx, lp_afs_username_map()); + if (afsname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + afsname = talloc_string_sub(tmp_ctx, + lp_afs_username_map(), + "%D", name_domain); + afsname = talloc_string_sub(tmp_ctx, afsname, + "%u", name_user); + afsname = talloc_string_sub(tmp_ctx, afsname, + "%U", name_user); + + { + struct dom_sid user_sid; + struct dom_sid_buf sidstr; + + sid_compose(&user_sid, base_info->domain_sid, base_info->rid); + afsname = talloc_string_sub( + tmp_ctx, + afsname, + "%s", + dom_sid_str_buf(&user_sid, &sidstr)); + } + + if (afsname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + if (!strlower_m(afsname)) { + status = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + DEBUG(10, ("Generating token for user %s\n", afsname)); + + cell = strchr(afsname, '@'); + + if (cell == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + *cell = '\0'; + cell += 1; + + token = afs_createtoken_str(afsname, cell); + if (token == NULL) { + status = NT_STATUS_OK; + goto out; + } + + talloc_steal(mem_ctx, token); + *_blob = data_blob_string_const_null(token); + + status = NT_STATUS_OK; +out: + TALLOC_FREE(tmp_ctx); + + return status; +} + +NTSTATUS extra_data_to_sid_array(const char *group_sid, + TALLOC_CTX *mem_ctx, + struct wbint_SidArray **_sid_array) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct wbint_SidArray *sid_array = NULL; + struct dom_sid *require_membership_of_sid = NULL; + uint32_t num_require_membership_of_sid = 0; + char *req_sid = NULL; + const char *p = NULL; + NTSTATUS status; + + if (_sid_array == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + *_sid_array = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sid_array = talloc_zero(tmp_ctx, struct wbint_SidArray); + if (sid_array == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + if (!group_sid || !group_sid[0]) { + /* NO sid supplied, all users may access */ + status = NT_STATUS_OK; + /* + * Always return an allocated wbint_SidArray, + * even if the array is empty. + */ + goto out; + } + + num_require_membership_of_sid = 0; + require_membership_of_sid = NULL; + p = group_sid; + + while (next_token_talloc(tmp_ctx, &p, &req_sid, ",")) { + struct dom_sid sid; + + if (!string_to_sid(&sid, req_sid)) { + DBG_ERR("check_info3_in_group: could not parse %s " + "as a SID!\n", req_sid); + status = NT_STATUS_INVALID_PARAMETER; + goto fail; + } + + status = add_sid_to_array(tmp_ctx, &sid, + &require_membership_of_sid, + &num_require_membership_of_sid); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("add_sid_to_array failed\n"); + goto fail; + } + } + + sid_array->num_sids = num_require_membership_of_sid; + sid_array->sids = talloc_move(sid_array, &require_membership_of_sid); + + status = NT_STATUS_OK; +out: + *_sid_array = talloc_move(mem_ctx, &sid_array); + +fail: + TALLOC_FREE(tmp_ctx); + + return status; +} + +static NTSTATUS check_info3_in_group(struct netr_SamInfo3 *info3, + struct wbint_SidArray *sid_array) +/** + * Check whether a user belongs to a group or list of groups. + * + * @param mem_ctx talloc memory context. + * @param info3 user information, including group membership info. + * @param group_sid One or more groups , separated by commas. + * + * @return NT_STATUS_OK on success, + * NT_STATUS_LOGON_FAILURE if the user does not belong, + * or other NT_STATUS_IS_ERR(status) for other kinds of failure. + */ +{ + size_t i; + struct security_token *token; + NTSTATUS status; + + /* Parse the 'required group' SID */ + + if (sid_array == NULL || sid_array->num_sids == 0) { + /* NO sid supplied, all users may access */ + return NT_STATUS_OK; + } + + /* + * This is a limited-use security_token for the purpose of + * checking the SID list below, so no claims need to be added + * and se_access_check() will never run. + */ + token = security_token_initialise(talloc_tos(), + CLAIMS_EVALUATION_INVALID_STATE); + if (token == NULL) { + DEBUG(0, ("talloc failed\n")); + return NT_STATUS_NO_MEMORY; + } + + status = sid_array_from_info3(token, info3, + &token->sids, + &token->num_sids, + true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!NT_STATUS_IS_OK(status = add_aliases(get_global_sam_sid(), + token)) + || !NT_STATUS_IS_OK(status = add_aliases(&global_sid_Builtin, + token))) { + DEBUG(3, ("could not add aliases: %s\n", + nt_errstr(status))); + return status; + } + + security_token_debug(DBGC_CLASS, 10, token); + + for (i=0; i<sid_array->num_sids; i++) { + struct dom_sid_buf buf; + DEBUG(10, ("Checking SID %s\n", + dom_sid_str_buf(&sid_array->sids[i], + &buf))); + if (nt_token_check_sid(&sid_array->sids[i], + token)) { + DEBUG(10, ("Access ok\n")); + return NT_STATUS_OK; + } + } + + /* Do not distinguish this error from a wrong username/pw */ + + return NT_STATUS_LOGON_FAILURE; +} + +struct winbindd_domain *find_auth_domain(uint8_t flags, + const char *domain_name) +{ + struct winbindd_domain *domain; + + if (IS_DC) { + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + DEBUG(3, ("Authentication for domain [%s] refused " + "as it is not a trusted domain\n", + domain_name)); + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return domain->routing_domain; + } + + if (strequal(domain_name, get_global_sam_name())) { + return find_domain_from_name_noinit(domain_name); + } + + if (lp_winbind_use_krb5_enterprise_principals()) { + /* + * If we use enterprise principals + * we always go through our primary domain + * and follow the WRONG_REALM replies. + */ + flags &= ~WBFLAG_PAM_CONTACT_TRUSTDOM; + } + + /* we can auth against trusted domains */ + if (flags & WBFLAG_PAM_CONTACT_TRUSTDOM) { + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + DEBUG(3, ("Authentication for domain [%s] skipped " + "as it is not a trusted domain\n", + domain_name)); + } else { + return domain; + } + } + + return find_our_domain(); +} + +static NTSTATUS get_password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 **_policy) +{ + NTSTATUS status; + struct samr_DomInfo1 *policy = NULL; + + if ( !winbindd_can_contact_domain( domain ) ) { + DBG_INFO("No inbound trust to contact domain %s\n", + domain->name); + return NT_STATUS_NOT_SUPPORTED; + } + + policy = talloc_zero(mem_ctx, struct samr_DomInfo1); + if (policy == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = wb_cache_password_policy(domain, mem_ctx, policy); + if (NT_STATUS_IS_ERR(status)) { + TALLOC_FREE(policy); + return status; + } + + *_policy = talloc_move(mem_ctx, &policy); + + return NT_STATUS_OK; +} + +static NTSTATUS get_max_bad_attempts_from_lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint16_t *lockout_threshold) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + struct samr_DomInfo12 lockout_policy; + + *lockout_threshold = 0; + + status = wb_cache_lockout_policy(domain, mem_ctx, &lockout_policy); + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + *lockout_threshold = lockout_policy.lockout_threshold; + + return NT_STATUS_OK; +} + +static NTSTATUS get_pwd_properties(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *password_properties) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + struct samr_DomInfo1 password_policy; + + *password_properties = 0; + + status = wb_cache_password_policy(domain, mem_ctx, &password_policy); + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + *password_properties = password_policy.password_properties; + + return NT_STATUS_OK; +} + +#ifdef HAVE_KRB5 + +static const char *generate_krb5_ccache(TALLOC_CTX *mem_ctx, + const char *type, + uid_t uid, + const char **user_ccache_file) +{ + /* accept FILE and WRFILE as krb5_cc_type from the client and then + * build the full ccname string based on the user's uid here - + * Guenther*/ + + const char *gen_cc = NULL; + + if (uid != -1) { + if (strequal(type, "FILE")) { + gen_cc = talloc_asprintf( + mem_ctx, "FILE:/tmp/krb5cc_%d", uid); + } + if (strequal(type, "WRFILE")) { + gen_cc = talloc_asprintf( + mem_ctx, "WRFILE:/tmp/krb5cc_%d", uid); + } + if (strequal(type, "KEYRING")) { + gen_cc = talloc_asprintf( + mem_ctx, "KEYRING:persistent:%d", uid); + } + if (strequal(type, "KCM")) { + gen_cc = talloc_asprintf(mem_ctx, + "KCM:%d", + uid); + } + + if (strnequal(type, "FILE:/", 6) || + strnequal(type, "WRFILE:/", 8) || + strnequal(type, "DIR:/", 5)) { + + /* we allow only one "%u" substitution */ + + char *p; + + p = strchr(type, '%'); + if (p != NULL) { + + p++; + + if (p != NULL && *p == 'u' && strchr(p, '%') == NULL) { + char uid_str[sizeof("18446744073709551615")]; + + snprintf(uid_str, sizeof(uid_str), "%u", uid); + + gen_cc = talloc_string_sub2(mem_ctx, + type, + "%u", + uid_str, + /* remove_unsafe_characters */ + false, + /* replace_once */ + true, + /* allow_trailing_dollar */ + false); + } + } + } + } + + *user_ccache_file = gen_cc; + + if (gen_cc == NULL) { + gen_cc = talloc_strdup(mem_ctx, "MEMORY:winbindd_pam_ccache"); + } + if (gen_cc == NULL) { + DEBUG(0,("out of memory\n")); + return NULL; + } + + DEBUG(10, ("using ccache: %s%s\n", gen_cc, + (*user_ccache_file == NULL) ? " (internal)":"")); + + return gen_cc; +} + +#endif + +uid_t get_uid_from_request(struct winbindd_request *request) +{ + uid_t uid; + + uid = request->data.auth.uid; + + if (uid == (uid_t)-1) { + DEBUG(1,("invalid uid: '%u'\n", (unsigned int)uid)); + return -1; + } + return uid; +} + +/********************************************************************** + Authenticate a user with a clear text password using Kerberos and fill up + ccache if required + **********************************************************************/ + +static NTSTATUS winbindd_raw_kerberos_login(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + const char *user, + const char *pass, + const char *krb5_cc_type, + uid_t uid, + struct netr_SamInfo6 **info6, + const char **_krb5ccname) +{ +#ifdef HAVE_KRB5 + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + krb5_error_code krb5_ret; + const char *cc = NULL; + const char *principal_s = NULL; + char *realm = NULL; + char *name_namespace = NULL; + char *name_domain = NULL; + char *name_user = NULL; + time_t ticket_lifetime = 0; + time_t renewal_until = 0; + time_t time_offset = 0; + const char *user_ccache_file; + struct PAC_LOGON_INFO *logon_info = NULL; + struct PAC_UPN_DNS_INFO *upn_dns_info = NULL; + struct PAC_DATA *pac_data = NULL; + struct PAC_DATA_CTR *pac_data_ctr = NULL; + const char *local_service; + uint32_t i; + struct netr_SamInfo6 *info6_copy = NULL; + char *canon_principal = NULL; + char *canon_realm = NULL; + bool ok; + + *info6 = NULL; + + if (domain->alt_name == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (_krb5ccname != NULL) { + *_krb5ccname = NULL; + } + + /* 1st step: + * prepare a krb5_cc_cache string for the user */ + + if (uid == -1) { + DEBUG(0,("no valid uid\n")); + } + + cc = generate_krb5_ccache(mem_ctx, + krb5_cc_type, + uid, + &user_ccache_file); + if (cc == NULL) { + return NT_STATUS_NO_MEMORY; + } + + + /* 2nd step: + * get kerberos properties */ + + if (domain->backend_data.ads_conn != NULL) { + time_offset = domain->backend_data.ads_conn->auth.time_offset; + } + + + /* 3rd step: + * do kerberos auth and setup ccache as the user */ + + ok = parse_domain_user(mem_ctx, + user, + &name_namespace, + &name_domain, + &name_user); + if (!ok) { + return NT_STATUS_INVALID_PARAMETER; + } + + realm = talloc_strdup(mem_ctx, domain->alt_name); + if (realm == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (!strupper_m(realm)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (lp_winbind_use_krb5_enterprise_principals() && + name_namespace[0] != '\0') + { + principal_s = talloc_asprintf(mem_ctx, + "%s@%s@%s", + name_user, + name_namespace, + realm); + } else { + principal_s = talloc_asprintf(mem_ctx, + "%s@%s", + name_user, + realm); + } + if (principal_s == NULL) { + return NT_STATUS_NO_MEMORY; + } + + local_service = talloc_asprintf(mem_ctx, "%s$@%s", + lp_netbios_name(), lp_realm()); + if (local_service == NULL) { + return NT_STATUS_NO_MEMORY; + } + + + /* if this is a user ccache, we need to act as the user to let the krb5 + * library handle the chown, etc. */ + + /************************ ENTERING NON-ROOT **********************/ + + if (user_ccache_file != NULL) { + set_effective_uid(uid); + DEBUG(10,("winbindd_raw_kerberos_login: uid is %d\n", uid)); + } + + result = kerberos_return_pac(mem_ctx, + principal_s, + pass, + time_offset, + &ticket_lifetime, + &renewal_until, + cc, + true, + true, + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME, + NULL, + local_service, + &canon_principal, + &canon_realm, + &pac_data_ctr); + if (user_ccache_file != NULL) { + gain_root_privilege(); + } + + /************************ RETURNED TO ROOT **********************/ + + if (!NT_STATUS_IS_OK(result)) { + goto failed; + } + + if (pac_data_ctr == NULL) { + goto failed; + } + + pac_data = pac_data_ctr->pac_data; + if (pac_data == NULL) { + goto failed; + } + + for (i=0; i < pac_data->num_buffers; i++) { + + if (pac_data->buffers[i].type == PAC_TYPE_LOGON_INFO) { + logon_info = pac_data->buffers[i].info->logon_info.info; + continue; + } + + if (pac_data->buffers[i].type == PAC_TYPE_UPN_DNS_INFO) { + upn_dns_info = &pac_data->buffers[i].info->upn_dns_info; + continue; + } + } + + if (logon_info == NULL) { + DEBUG(10,("Missing logon_info in ticket of %s\n", + principal_s)); + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(10,("winbindd_raw_kerberos_login: winbindd validated ticket of %s\n", + principal_s)); + + result = create_info6_from_pac(mem_ctx, logon_info, + upn_dns_info, &info6_copy); + if (!NT_STATUS_IS_OK(result)) { + goto failed; + } + + /* if we had a user's ccache then return that string for the pam + * environment */ + + if (user_ccache_file != NULL) { + + if (_krb5ccname != NULL) { + *_krb5ccname = talloc_steal(mem_ctx, user_ccache_file); + } + + result = add_ccache_to_list(principal_s, + cc, + user, + pass, + realm, + uid, + time(NULL), + ticket_lifetime, + renewal_until, + false, + canon_principal, + canon_realm); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_raw_kerberos_login: failed to add ccache to list: %s\n", + nt_errstr(result))); + } + } else { + + /* need to delete the memory cred cache, it is not used anymore */ + + krb5_ret = ads_kdestroy(cc); + if (krb5_ret) { + DEBUG(3,("winbindd_raw_kerberos_login: " + "could not destroy krb5 credential cache: " + "%s\n", error_message(krb5_ret))); + } + + } + *info6 = info6_copy; + return NT_STATUS_OK; + +failed: + /* + * Do not delete an existing valid credential cache, if the user + * e.g. enters a wrong password + */ + if ((strequal(krb5_cc_type, "FILE") || strequal(krb5_cc_type, "WRFILE")) + && user_ccache_file != NULL) { + return result; + } + + /* we could have created a new credential cache with a valid tgt in it + * but we weren't able to get or verify the service ticket for this + * local host and therefore didn't get the PAC, we need to remove that + * cache entirely now */ + + krb5_ret = ads_kdestroy(cc); + if (krb5_ret) { + DEBUG(3,("winbindd_raw_kerberos_login: " + "could not destroy krb5 credential cache: " + "%s\n", error_message(krb5_ret))); + } + + if (!NT_STATUS_IS_OK(remove_ccache(user))) { + DEBUG(3,("winbindd_raw_kerberos_login: " + "could not remove ccache for user %s\n", + user)); + } + + return result; +#else + return NT_STATUS_NOT_SUPPORTED; +#endif /* HAVE_KRB5 */ +} + +/**************************************************************** +****************************************************************/ + +bool check_request_flags(uint32_t flags) +{ + uint32_t flags_edata = WBFLAG_PAM_AFS_TOKEN | + WBFLAG_PAM_INFO3_TEXT | + WBFLAG_PAM_INFO3_NDR; + + if ( ( (flags & flags_edata) == WBFLAG_PAM_AFS_TOKEN) || + ( (flags & flags_edata) == WBFLAG_PAM_INFO3_NDR) || + ( (flags & flags_edata) == WBFLAG_PAM_INFO3_TEXT)|| + !(flags & flags_edata) ) { + return true; + } + + DEBUG(1, ("check_request_flags: invalid request flags[0x%08X]\n", + flags)); + + return false; +} + +/**************************************************************** +****************************************************************/ + +NTSTATUS append_auth_data(TALLOC_CTX *mem_ctx, + struct winbindd_response *resp, + uint32_t request_flags, + uint16_t validation_level, + union netr_Validation *validation, + const char *name_domain, + const char *name_user) +{ + struct netr_SamInfo3 *info3 = NULL; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + + result = map_validation_to_info3(talloc_tos(), + validation_level, + validation, + &info3); + if (!NT_STATUS_IS_OK(result)) { + goto out; + } + + if (request_flags & WBFLAG_PAM_USER_SESSION_KEY) { + memcpy(resp->data.auth.user_session_key, + info3->base.key.key, + sizeof(resp->data.auth.user_session_key) + /* 16 */); + } + + if (request_flags & WBFLAG_PAM_LMKEY) { + memcpy(resp->data.auth.first_8_lm_hash, + info3->base.LMSessKey.key, + sizeof(resp->data.auth.first_8_lm_hash) + /* 8 */); + } + + if (request_flags & WBFLAG_PAM_UNIX_NAME) { + char *unix_username = NULL; + result = append_unix_username(validation_level, + validation, + name_domain, + name_user, + mem_ctx, + &unix_username); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("Failed to append Unix Username: %s\n", + nt_errstr(result))); + goto out; + } + fstrcpy(resp->data.auth.unix_username, unix_username); + TALLOC_FREE(unix_username); + } + + /* currently, anything from here on potentially overwrites extra_data. */ + + if (request_flags & WBFLAG_PAM_INFO3_NDR) { + result = append_info3_as_ndr(mem_ctx, resp, info3); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("Failed to append INFO3 (NDR): %s\n", + nt_errstr(result))); + goto out; + } + } + + if (request_flags & WBFLAG_PAM_INFO3_TEXT) { + result = append_info3_as_txt(mem_ctx, resp, + validation_level, + validation); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("Failed to append INFO3 (TXT): %s\n", + nt_errstr(result))); + goto out; + } + } + + if (request_flags & WBFLAG_PAM_AFS_TOKEN) { + DATA_BLOB blob = data_blob_null; + result = append_afs_token(validation_level, + validation, + name_domain, + name_user, + mem_ctx, + &blob); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("Failed to append AFS token: %s\n", + nt_errstr(result))); + goto out; + } + resp->extra_data.data = blob.data; + resp->length += blob.length; + } + + result = NT_STATUS_OK; +out: + TALLOC_FREE(info3); + return result; +} + +static NTSTATUS winbindd_dual_pam_auth_cached(struct winbindd_domain *domain, + bool krb5_auth, + const char *user, + const char *pass, + const char *krb5_cc_type, + uid_t uid, + TALLOC_CTX *mem_ctx, + uint16_t *_validation_level, + union netr_Validation **_validation, + const char **_krb5ccname) +{ + TALLOC_CTX *tmp_ctx = NULL; + NTSTATUS result = NT_STATUS_LOGON_FAILURE; + uint16_t max_allowed_bad_attempts; + char *name_namespace = NULL; + char *name_domain = NULL; + char *name_user = NULL; + struct dom_sid sid; + enum lsa_SidType type; + uchar new_nt_pass[NT_HASH_LEN]; + const uint8_t *cached_nt_pass; + const uint8_t *cached_salt; + struct netr_SamInfo3 *my_info3; + time_t kickoff_time, must_change_time; + bool password_good = false; + bool ok; +#ifdef HAVE_KRB5 + struct winbindd_tdc_domain *tdc_domain = NULL; +#endif + + if (_validation == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + *_validation = NULL; + + if (_krb5ccname != NULL) { + *_krb5ccname = NULL; + } + + DEBUG(10,("winbindd_dual_pam_auth_cached\n")); + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Parse domain and username */ + + ok = parse_domain_user(tmp_ctx, + user, + &name_namespace, + &name_domain, + &name_user); + if (!ok) { + DBG_DEBUG("parse_domain_user failed\n"); + result = NT_STATUS_NO_SUCH_USER; + goto out; + } + + if (!lookup_cached_name(name_namespace, + name_domain, + name_user, + &sid, + &type)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: no such user in the cache\n")); + result = NT_STATUS_NO_SUCH_USER; + goto out; + } + + if (type != SID_NAME_USER) { + DEBUG(10,("winbindd_dual_pam_auth_cached: not a user (%s)\n", sid_type_lookup(type))); + result = NT_STATUS_LOGON_FAILURE; + goto out; + } + + result = winbindd_get_creds(domain, + tmp_ctx, + &sid, + &my_info3, + &cached_nt_pass, + &cached_salt); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get creds: %s\n", nt_errstr(result))); + goto out; + } + + E_md4hash(pass, new_nt_pass); + + dump_data_pw("new_nt_pass", new_nt_pass, NT_HASH_LEN); + dump_data_pw("cached_nt_pass", cached_nt_pass, NT_HASH_LEN); + if (cached_salt) { + dump_data_pw("cached_salt", cached_salt, NT_HASH_LEN); + } + + if (cached_salt) { + /* In this case we didn't store the nt_hash itself, + but the MD5 combination of salt + nt_hash. */ + uchar salted_hash[NT_HASH_LEN]; + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + result = gnutls_error_to_ntstatus( + rc, NT_STATUS_HASH_NOT_SUPPORTED); + goto out; + } + + rc = gnutls_hash(hash_hnd, cached_salt, 16); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + result = gnutls_error_to_ntstatus( + rc, NT_STATUS_HASH_NOT_SUPPORTED); + goto out; + } + rc = gnutls_hash(hash_hnd, new_nt_pass, 16); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + result = gnutls_error_to_ntstatus( + rc, NT_STATUS_HASH_NOT_SUPPORTED); + goto out; + } + gnutls_hash_deinit(hash_hnd, salted_hash); + + password_good = mem_equal_const_time(cached_nt_pass, salted_hash, + NT_HASH_LEN); + } else { + /* Old cached cred - direct store of nt_hash (bad bad bad !). */ + password_good = mem_equal_const_time(cached_nt_pass, new_nt_pass, + NT_HASH_LEN); + } + + if (password_good) { + + /* User *DOES* know the password, update logon_time and reset + * bad_pw_count */ + + my_info3->base.user_flags |= NETLOGON_CACHED_ACCOUNT; + + if (my_info3->base.acct_flags & ACB_AUTOLOCK) { + result = NT_STATUS_ACCOUNT_LOCKED_OUT; + goto out; + } + + if (my_info3->base.acct_flags & ACB_DISABLED) { + result = NT_STATUS_ACCOUNT_DISABLED; + goto out; + } + + if (my_info3->base.acct_flags & ACB_WSTRUST) { + result = NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT; + goto out; + } + + if (my_info3->base.acct_flags & ACB_SVRTRUST) { + result = NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT; + goto out; + } + + if (my_info3->base.acct_flags & ACB_DOMTRUST) { + result = NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT; + goto out; + } + + if (!(my_info3->base.acct_flags & ACB_NORMAL)) { + DEBUG(0,("winbindd_dual_pam_auth_cached: what's wrong with that one?: 0x%08x\n", + my_info3->base.acct_flags)); + result = NT_STATUS_LOGON_FAILURE; + goto out; + } + + kickoff_time = nt_time_to_unix(my_info3->base.kickoff_time); + if (kickoff_time != 0 && time(NULL) > kickoff_time) { + result = NT_STATUS_ACCOUNT_EXPIRED; + goto out; + } + + must_change_time = nt_time_to_unix(my_info3->base.force_password_change); + if (must_change_time != 0 && must_change_time < time(NULL)) { + /* we allow grace logons when the password has expired */ + my_info3->base.user_flags |= NETLOGON_GRACE_LOGON; + /* return NT_STATUS_PASSWORD_EXPIRED; */ + goto success; + } + +#ifdef HAVE_KRB5 + if ((krb5_auth) && + ((tdc_domain = wcache_tdc_fetch_domain(tmp_ctx, name_domain)) != NULL) && + ((tdc_domain->trust_type & LSA_TRUST_TYPE_UPLEVEL) || + /* used to cope with the case winbindd starting without network. */ + !strequal(tdc_domain->domain_name, tdc_domain->dns_name))) { + const char *cc = NULL; + char *realm = NULL; + const char *principal_s = NULL; + const char *user_ccache_file; + + if (domain->alt_name == NULL) { + result = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + if (uid == -1) { + DEBUG(0,("winbindd_dual_pam_auth_cached: invalid uid\n")); + result = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + cc = generate_krb5_ccache(tmp_ctx, + krb5_cc_type, + uid, + &user_ccache_file); + if (cc == NULL) { + result = NT_STATUS_NO_MEMORY; + goto out; + } + + realm = talloc_strdup(tmp_ctx, domain->alt_name); + if (realm == NULL) { + result = NT_STATUS_NO_MEMORY; + goto out; + } + + if (!strupper_m(realm)) { + result = NT_STATUS_INVALID_PARAMETER; + goto out; + } + + principal_s = talloc_asprintf(tmp_ctx, "%s@%s", name_user, realm); + if (principal_s == NULL) { + result = NT_STATUS_NO_MEMORY; + goto out; + } + + if (user_ccache_file != NULL) { + + if (_krb5ccname != NULL) { + *_krb5ccname = talloc_move(mem_ctx, + &user_ccache_file); + } + + result = add_ccache_to_list(principal_s, + cc, + user, + pass, + realm, + uid, + time(NULL), + time(NULL) + lp_winbind_cache_time(), + time(NULL) + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME, + true, + principal_s, + realm); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: failed " + "to add ccache to list: %s\n", + nt_errstr(result))); + } + } + } +#endif /* HAVE_KRB5 */ + success: + /* FIXME: we possibly should handle logon hours as well (does xp when + * offline?) see auth/auth_sam.c:sam_account_ok for details */ + + unix_to_nt_time(&my_info3->base.logon_time, time(NULL)); + my_info3->base.bad_password_count = 0; + + result = winbindd_update_creds_by_info3(domain, + user, + pass, + my_info3); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1,("winbindd_dual_pam_auth_cached: failed to update creds: %s\n", + nt_errstr(result))); + goto out; + } + + result = map_info3_to_validation(mem_ctx, + my_info3, + _validation_level, + _validation); + if (!NT_STATUS_IS_OK(result)) { + DBG_ERR("map_info3_to_validation failed: %s\n", + nt_errstr(result)); + goto out; + } + + result = NT_STATUS_OK; + goto out; + } + + /* User does *NOT* know the correct password, modify info3 accordingly, but only if online */ + if (domain->online == false) { + goto failed; + } + + /* failure of this is not critical */ + result = get_max_bad_attempts_from_lockout_policy(domain, tmp_ctx, &max_allowed_bad_attempts); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get max_allowed_bad_attempts. " + "Won't be able to honour account lockout policies\n")); + } + + /* increase counter */ + my_info3->base.bad_password_count++; + + if (max_allowed_bad_attempts == 0) { + goto failed; + } + + /* lockout user */ + if (my_info3->base.bad_password_count >= max_allowed_bad_attempts) { + + uint32_t password_properties; + + result = get_pwd_properties(domain, tmp_ctx, &password_properties); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get password properties.\n")); + } + + if ((my_info3->base.rid != DOMAIN_RID_ADMINISTRATOR) || + (password_properties & DOMAIN_PASSWORD_LOCKOUT_ADMINS)) { + my_info3->base.acct_flags |= ACB_AUTOLOCK; + } + } + +failed: + result = winbindd_update_creds_by_info3(domain, user, NULL, my_info3); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("winbindd_dual_pam_auth_cached: failed to update creds %s\n", + nt_errstr(result))); + } + + result = NT_STATUS_LOGON_FAILURE; + +out: + TALLOC_FREE(tmp_ctx); + + return result; +} + +static NTSTATUS winbindd_dual_pam_auth_kerberos(struct winbindd_domain *domain, + const char *user, + const char *pass, + const char *krb5_cc_type, + uid_t uid, + TALLOC_CTX *mem_ctx, + uint16_t *_validation_level, + union netr_Validation **_validation, + const char **_krb5ccname) +{ + struct netr_SamInfo6 *info6 = NULL; + struct winbindd_domain *contact_domain; + char *name_namespace = NULL; + char *name_domain = NULL; + char *name_user = NULL; + NTSTATUS result; + bool ok; + + DEBUG(10,("winbindd_dual_pam_auth_kerberos\n")); + + /* Parse domain and username */ + + ok = parse_domain_user(mem_ctx, + user, + &name_namespace, + &name_domain, + &name_user); + if (!ok) { + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + /* what domain should we contact? */ + + if (lp_winbind_use_krb5_enterprise_principals()) { + contact_domain = find_auth_domain(0, name_namespace); + } else { + contact_domain = find_domain_from_name(name_namespace); + } + if (contact_domain == NULL) { + DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n", + user, name_domain, name_user, name_namespace)); + result = NT_STATUS_NO_SUCH_USER; + goto done; + } + + if (contact_domain->initialized && + contact_domain->active_directory) { + goto try_login; + } + + if (!contact_domain->initialized) { + init_dc_connection(contact_domain, false); + } + + if (!contact_domain->active_directory) { + DEBUG(3,("krb5 auth requested but domain (%s) is not Active Directory\n", + contact_domain->name)); + return NT_STATUS_INVALID_LOGON_TYPE; + } +try_login: + result = winbindd_raw_kerberos_login( + mem_ctx, + contact_domain, + user, + pass, + krb5_cc_type, + uid, + &info6, + _krb5ccname); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + result = map_info6_to_validation(mem_ctx, + info6, + _validation_level, + _validation); + TALLOC_FREE(info6); + if (!NT_STATUS_IS_OK(result)) { + DBG_ERR("map_info6_to_validation failed: %s\n", + nt_errstr(result)); + } + +done: + return result; +} + +static NTSTATUS winbindd_dual_auth_passdb(TALLOC_CTX *mem_ctx, + uint32_t logon_parameters, + const char *domain, + const char *user, + const uint64_t logon_id, + const char *client_name, + const int client_pid, + const DATA_BLOB *challenge, + const DATA_BLOB *lm_resp, + const DATA_BLOB *nt_resp, + const struct tsocket_address *remote, + const struct tsocket_address *local, + bool interactive, + uint8_t *pauthoritative, + struct netr_SamInfo3 **pinfo3) +{ + struct auth_context *auth_context; + struct auth_serversupplied_info *server_info; + struct auth_usersupplied_info *user_info = NULL; + struct netr_SamInfo3 *info3; + NTSTATUS status; + bool ok; + TALLOC_CTX *frame = talloc_stackframe(); + + /* + * We are authoritative by default + */ + *pauthoritative = 1; + + status = make_user_info(frame, &user_info, user, user, domain, domain, + lp_netbios_name(), remote, local, + "winbind", + lm_resp, nt_resp, NULL, NULL, + NULL, AUTH_PASSWORD_RESPONSE); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("make_user_info failed: %s\n", nt_errstr(status))); + TALLOC_FREE(frame); + return status; + } + + user_info->logon_parameters = logon_parameters; + user_info->logon_id = logon_id; + user_info->auth_description = talloc_asprintf( + frame, "PASSDB, %s, %d", client_name, client_pid); + if (user_info->auth_description == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + /* We don't want to come back to winbindd or to do PAM account checks */ + user_info->flags |= USER_INFO_INFO3_AND_NO_AUTHZ; + + if (interactive) { + user_info->flags |= USER_INFO_INTERACTIVE_LOGON; + } + + status = make_auth3_context_for_winbind(frame, &auth_context); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("make_auth3_context_for_winbind failed: %s\n", + nt_errstr(status)); + TALLOC_FREE(frame); + return status; + } + + ok = auth3_context_set_challenge(auth_context, + challenge->data, "fixed"); + if (!ok) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + status = auth_check_ntlm_password(mem_ctx, + auth_context, + user_info, + &server_info, + pauthoritative); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + info3 = talloc_zero(mem_ctx, struct netr_SamInfo3); + if (info3 == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + status = serverinfo_to_SamInfo3(server_info, info3); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + TALLOC_FREE(info3); + DEBUG(0, ("serverinfo_to_SamInfo3 failed: %s\n", + nt_errstr(status))); + return status; + } + + *pinfo3 = info3; + DBG_DEBUG("Authenticating user %s\\%s returned %s\n", + domain, + user, + nt_errstr(status)); + TALLOC_FREE(frame); + return status; +} + +static NTSTATUS winbind_samlogon_retry_loop(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t logon_parameters, + const char *username, + const char *password, + const char *domainname, + const char *workstation, + const uint64_t logon_id, + bool plaintext_given, + DATA_BLOB chal, + DATA_BLOB lm_response, + DATA_BLOB nt_response, + bool interactive, + uint8_t *authoritative, + uint32_t *flags, + uint16_t *_validation_level, + union netr_Validation **_validation) +{ + int attempts = 0; + int netr_attempts = 0; + bool retry = false; + bool valid_result = false; + NTSTATUS result; + enum netr_LogonInfoClass logon_type_i; + enum netr_LogonInfoClass logon_type_n; + uint16_t validation_level = UINT16_MAX; + union netr_Validation *validation = NULL; + TALLOC_CTX *base_ctx = NULL; + struct netr_SamBaseInfo *base_info = NULL; + + do { + struct rpc_pipe_client *netlogon_pipe; + struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL; + + /* + * We should always reset authoritative to 1 + * before calling a server again. + * + * Otherwise we could treat a local problem as + * non-authoritative. + */ + *authoritative = 1; + + retry = false; + + D_DEBUG("Creating a DCERPC netlogon connection for SAM logon. " + "netlogon attempt: %d, samlogon attempt: %d.\n", + netr_attempts, + attempts); + result = cm_connect_netlogon_secure(domain, &netlogon_pipe, + &netlogon_creds_ctx); + + if (NT_STATUS_EQUAL(result, + NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) { + /* + * This means we don't have a trust account. + */ + *authoritative = 0; + result = NT_STATUS_NO_SUCH_USER; + break; + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(3,("Could not open handle to NETLOGON pipe " + "(error: %s, attempts: %d)\n", + nt_errstr(result), netr_attempts)); + + reset_cm_connection_on_error(domain, NULL, result); + + /* After the first retry always close the connection */ + if (netr_attempts > 0) { + DEBUG(3, ("This is again a problem for this " + "particular call, forcing the close " + "of this connection\n")); + invalidate_cm_connection(domain); + } + + /* After the second retry failover to the next DC */ + if (netr_attempts > 1) { + /* + * If the netlogon server is not reachable then + * it is possible that the DC is rebuilding + * sysvol and shutdown netlogon for that time. + * We should failover to the next dc. + */ + DEBUG(3, ("This is the third problem for this " + "particular call, adding DC to the " + "negative cache list: %s %s\n", domain->name, domain->dcname)); + add_failed_connection_entry(domain->name, + domain->dcname, + result); + saf_delete(domain->name); + } + + /* Only allow 3 retries */ + if (netr_attempts < 3) { + DEBUG(3, ("The connection to netlogon " + "failed, retrying\n")); + netr_attempts++; + retry = true; + continue; + } + return result; + } + + logon_type_i = NetlogonInteractiveInformation; + logon_type_n = NetlogonNetworkInformation; + if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + logon_type_i = NetlogonInteractiveTransitiveInformation; + logon_type_n = NetlogonNetworkTransitiveInformation; + } + + if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + logon_type_i = NetlogonInteractiveTransitiveInformation; + logon_type_n = NetlogonNetworkTransitiveInformation; + } + + if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) { + logon_type_i = NetlogonInteractiveInformation; + logon_type_n = NetlogonNetworkInformation; + } + + if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN) { + logon_type_i = NetlogonInteractiveInformation; + logon_type_n = NetlogonNetworkInformation; + } + + netr_attempts = 0; + if (plaintext_given) { + result = rpccli_netlogon_password_logon( + netlogon_creds_ctx, + netlogon_pipe->binding_handle, + mem_ctx, + logon_parameters, + domainname, + username, + password, + workstation, + logon_id, + logon_type_i, + authoritative, + flags, + &validation_level, + &validation); + } else if (interactive) { + result = rpccli_netlogon_interactive_logon( + netlogon_creds_ctx, + netlogon_pipe->binding_handle, + mem_ctx, + logon_parameters, + username, + domainname, + workstation, + logon_id, + lm_response, + nt_response, + logon_type_i, + authoritative, + flags, + &validation_level, + &validation); + } else { + result = rpccli_netlogon_network_logon( + netlogon_creds_ctx, + netlogon_pipe->binding_handle, + mem_ctx, + logon_parameters, + username, + domainname, + workstation, + logon_id, + chal, + lm_response, + nt_response, + logon_type_n, + authoritative, + flags, + &validation_level, + &validation); + } + + /* + * we increment this after the "feature negotiation" + * for can_do_samlogon_ex and can_do_validation6 + */ + attempts += 1; + + /* We have to try a second time as cm_connect_netlogon + might not yet have noticed that the DC has killed + our connection. */ + + retry = reset_cm_connection_on_error(domain, + netlogon_pipe->binding_handle, + result); + if (retry) { + DBG_PREFIX(attempts > 1 ? DBGLVL_NOTICE : DBGLVL_INFO, ( + "This is problem %d for this " + "particular call," + "DOMAIN[%s] DC[%s] - %s\n", + attempts, + domain->name, + domain->dcname, + nt_errstr(result))); + continue; + } + + valid_result = true; + + if (NT_STATUS_EQUAL(result, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + /* + * Got DCERPC_FAULT_OP_RNG_ERROR for SamLogon + * (no Ex). This happens against old Samba + * DCs, if LogonSamLogonEx() fails with an error + * e.g. NT_STATUS_NO_SUCH_USER or NT_STATUS_WRONG_PASSWORD. + * + * The server will log something like this: + * api_net_sam_logon_ex: Failed to marshall NET_R_SAM_LOGON_EX. + * + * This sets the whole connection into a fault_state mode + * and all following request get NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE. + * + * This also happens to our retry with LogonSamLogonWithFlags() + * and LogonSamLogon(). + * + * In order to recover from this situation, we need to + * drop the connection. + */ + invalidate_cm_connection(domain); + result = NT_STATUS_LOGON_FAILURE; + break; + } + + } while ( (attempts < 3) && retry ); + + if (!valid_result) { + /* + * This matches what windows does. In a chain of transitive + * trusts the ACCESS_DENIED/authoritative=0 is not propagated + * instead of NT_STATUS_NO_LOGON_SERVERS/authoritative=1 is + * passed along the chain if there's no other DC is available. + */ + DBG_WARNING("Mapping %s/authoritative=%u to " + "NT_STATUS_NO_LOGON_SERVERS/authoritative=1 for" + "USERNAME[%s] USERDOMAIN[%s] REMOTE-DOMAIN[%s] \n", + nt_errstr(result), + *authoritative, + username, + domainname, + domain->name); + *authoritative = 1; + return NT_STATUS_NO_LOGON_SERVERS; + } + + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + switch (validation_level) { + case 3: + base_ctx = validation->sam3; + base_info = &validation->sam3->base; + break; + case 6: + base_ctx = validation->sam6; + base_info = &validation->sam6->base; + break; + default: + smb_panic(__location__); + } + + if (base_info->acct_flags == 0 || base_info->account_name.string == NULL) { + struct dom_sid user_sid; + struct dom_sid_buf sid_buf; + const char *acct_flags_src = "server"; + const char *acct_name_src = "server"; + + /* + * Handle the case where a NT4 DC does not fill in the acct_flags in + * the samlogon reply info3. Yes, in 2021, there are still admins + * around with real NT4 DCs. + * + * We used to call dcerpc_samr_QueryUserInfo(level=16) to fetch + * acct_flags, but as NT4 DCs reject authentication with workstation + * accounts with NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT, even if + * MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT is specified, we only ever got + * ACB_NORMAL back (maybe with ACB_PWNOEXP in addition). + * + * For network logons NT4 DCs also skip the + * account_name, so we have to fallback to the + * one given by the client. + */ + + if (base_info->acct_flags == 0) { + base_info->acct_flags = ACB_NORMAL; + if (base_info->force_password_change == NTTIME_MAX) { + base_info->acct_flags |= ACB_PWNOEXP; + } + acct_flags_src = "calculated"; + } + + if (base_info->account_name.string == NULL) { + base_info->account_name.string = talloc_strdup(base_ctx, + username); + if (base_info->account_name.string == NULL) { + TALLOC_FREE(validation); + return NT_STATUS_NO_MEMORY; + } + acct_name_src = "client"; + } + + sid_compose(&user_sid, base_info->domain_sid, base_info->rid); + + DBG_DEBUG("Fallback to %s_acct_flags[0x%x] %s_acct_name[%s] for %s\n", + acct_flags_src, + base_info->acct_flags, + acct_name_src, + base_info->account_name.string, + dom_sid_str_buf(&user_sid, &sid_buf)); + } + + *_validation_level = validation_level; + *_validation = validation; + return NT_STATUS_OK; +} + +static NTSTATUS nt_dual_auth_passdb(TALLOC_CTX *mem_ctx, + fstring name_user, + fstring name_domain, + const char *pass, + uint64_t logon_id, + const char *client_name, + const int client_pid, + const struct tsocket_address *remote, + const struct tsocket_address *local, + uint8_t *authoritative, + struct netr_SamInfo3 **info3) +{ + unsigned char local_nt_response[24]; + uchar chal[8]; + DATA_BLOB chal_blob; + DATA_BLOB lm_resp; + DATA_BLOB nt_resp; + + /* do password magic */ + + generate_random_buffer(chal, sizeof(chal)); + chal_blob = data_blob_const(chal, sizeof(chal)); + + if (lp_client_ntlmv2_auth()) { + DATA_BLOB server_chal; + DATA_BLOB names_blob; + server_chal = data_blob_const(chal, 8); + + /* note that the 'workgroup' here is for the local + machine. The 'server name' must match the + 'workstation' passed to the actual SamLogon call. + */ + names_blob = NTLMv2_generate_names_blob(mem_ctx, + lp_netbios_name(), + lp_workgroup()); + + if (!SMBNTLMv2encrypt(mem_ctx, name_user, name_domain, + pass, &server_chal, &names_blob, + &lm_resp, &nt_resp, NULL, NULL)) { + data_blob_free(&names_blob); + DEBUG(0, ("SMBNTLMv2encrypt() failed!\n")); + return NT_STATUS_NO_MEMORY; + } + data_blob_free(&names_blob); + } else { + int rc; + lm_resp = data_blob_null; + + rc = SMBNTencrypt(pass, chal, local_nt_response); + if (rc != 0) { + DEBUG(0, ("SMBNTencrypt() failed!\n")); + return gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + nt_resp = data_blob_talloc(mem_ctx, local_nt_response, + sizeof(local_nt_response)); + } + + return winbindd_dual_auth_passdb(talloc_tos(), 0, name_domain, + name_user, logon_id, client_name, + client_pid, &chal_blob, &lm_resp, + &nt_resp, remote, local, + true, /* interactive */ + authoritative, info3); +} + +static NTSTATUS winbindd_dual_pam_auth_samlogon( + TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + const char *user, + const char *pass, + uint64_t logon_id, + const char *client_name, + const int client_pid, + uint32_t request_flags, + const struct tsocket_address *remote, + const struct tsocket_address *local, + uint16_t *_validation_level, + union netr_Validation **_validation) +{ + char *name_namespace = NULL; + char *name_domain = NULL; + char *name_user = NULL; + NTSTATUS result; + uint8_t authoritative = 1; + uint32_t flags = 0; + uint16_t validation_level = 0; + union netr_Validation *validation = NULL; + bool ok; + + DEBUG(10,("winbindd_dual_pam_auth_samlogon\n")); + + /* Parse domain and username */ + + ok = parse_domain_user(mem_ctx, + user, + &name_namespace, + &name_domain, + &name_user); + if (!ok) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * We check against domain->name instead of + * name_domain, as find_auth_domain() -> + * find_domain_from_name_noinit() already decided + * that we are in a child for the correct domain. + * + * name_domain can also be lp_realm() + * we need to check against domain->name. + */ + if (strequal(domain->name, get_global_sam_name())) { + struct netr_SamInfo3 *info3 = NULL; + + result = nt_dual_auth_passdb(mem_ctx, name_user, name_domain, + pass, logon_id, client_name, + client_pid, remote, local, + &authoritative, &info3); + + /* + * We need to try the remote NETLOGON server if this is + * not authoritative (for example on the RODC). + */ + if (authoritative != 0) { + if (!NT_STATUS_IS_OK(result)) { + return result; + } + result = map_info3_to_validation(mem_ctx, + info3, + &validation_level, + &validation); + TALLOC_FREE(info3); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + goto done; + } + } + + /* check authentication loop */ + + result = winbind_samlogon_retry_loop(domain, + mem_ctx, + 0, + name_user, + pass, + name_domain, + lp_netbios_name(), + logon_id, + true, /* plaintext_given */ + data_blob_null, + data_blob_null, data_blob_null, + true, /* interactive */ + &authoritative, + &flags, + &validation_level, + &validation); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + +done: + *_validation_level = validation_level; + *_validation = validation; + + return NT_STATUS_OK; +} + +/* + * @brief generate an authentication message in the logs. + * + */ +static void log_authentication( + TALLOC_CTX *mem_ctx, + const struct winbindd_domain *domain, + const char *client_name, + pid_t client_pid, + uint16_t validation_level, + union netr_Validation *validation, + const struct timeval start_time, + const uint64_t logon_id, + const char *command, + const char *user_name, + const char *domain_name, + const char *workstation, + const DATA_BLOB lm_resp, + const DATA_BLOB nt_resp, + const struct tsocket_address *remote, + const struct tsocket_address *local, + NTSTATUS result) +{ + struct auth_usersupplied_info *ui = NULL; + struct dom_sid *sid = NULL; + struct loadparm_context *lp_ctx = NULL; + struct imessaging_context *msg_ctx = NULL; + struct netr_SamBaseInfo *base_info = NULL; + + if (validation != NULL) { + switch (validation_level) { + case 3: + base_info = &validation->sam3->base; + break; + case 6: + base_info = &validation->sam6->base; + break; + default: + DBG_WARNING("Unexpected validation level '%d'\n", + validation_level); + break; + } + } + + ui = talloc_zero(mem_ctx, struct auth_usersupplied_info); + ui->logon_id = logon_id; + ui->service_description = "winbind"; + ui->password.response.nt.length = nt_resp.length; + ui->password.response.nt.data = nt_resp.data; + ui->password.response.lanman.length = lm_resp.length; + ui->password.response.lanman.data = lm_resp.data; + if (nt_resp.length == 0 && lm_resp.length == 0) { + ui->password_state = AUTH_PASSWORD_PLAIN; + } else { + ui->password_state = AUTH_PASSWORD_RESPONSE; + } + /* + * In the event of a failure ui->auth_description will be null, + * the logging code handles this correctly so it can be ignored. + */ + ui->auth_description = talloc_asprintf( + ui, + "%s, %s, %d", + command, + client_name, + client_pid); + if (ui->auth_description == NULL) { + DBG_ERR("OOM Unable to create auth_description\n"); + } + ui->client.account_name = user_name; + ui->client.domain_name = domain_name; + ui->workstation_name = workstation; + ui->remote_host = remote; + ui->local_host = local; + + if (base_info != NULL) { + sid = dom_sid_dup(ui, base_info->domain_sid); + if (sid != NULL) { + sid_append_rid(sid, base_info->rid); + } + } + + if (lp_auth_event_notification()) { + lp_ctx = loadparm_init_s3(ui, loadparm_s3_helpers()); + msg_ctx = imessaging_client_init( + ui, lp_ctx, global_event_context()); + } + log_authentication_event( + msg_ctx, + lp_ctx, + &start_time, + ui, + result, + base_info != NULL ? base_info->logon_domain.string : "", + base_info != NULL ? base_info->account_name.string : "", + sid, + NULL /* client_audit_info */, + NULL /* server_audit_info */); + TALLOC_FREE(ui); +} + +NTSTATUS _wbint_PamAuth(struct pipes_struct *p, + struct wbint_PamAuth *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS result = NT_STATUS_LOGON_FAILURE; + NTSTATUS krb5_result = NT_STATUS_OK; + char *name_namespace = NULL; + char *name_domain = NULL; + char *name_user = NULL; + char *mapped_user = NULL; + const char *domain_user = NULL; + uint16_t validation_level = UINT16_MAX; + union netr_Validation *validation = NULL; + struct netr_SamBaseInfo *base_info = NULL; + NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL; + bool ok; + uint64_t logon_id = 0; + const struct timeval start_time = timeval_current(); + const struct tsocket_address *remote = NULL; + const struct tsocket_address *local = NULL; + const char *krb5ccname = NULL; + uid_t uid; + pid_t client_pid; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + /* Cut client_pid to 32bit */ + client_pid = r->in.client_pid; + if ((uint64_t)client_pid != r->in.client_pid) { + DBG_DEBUG("pid out of range\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + /* Cut uid to 32bit */ + uid = r->in.info->uid; + if ((uint64_t)uid != r->in.info->uid) { + DBG_DEBUG("uid out of range\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * Generate a logon_id for this session. + */ + logon_id = generate_random_u64(); + remote = dcesrv_connection_get_remote_address(p->dce_call->conn); + local = dcesrv_connection_get_local_address(p->dce_call->conn); + DEBUG(3, ("[%"PRIu32"]: dual pam auth %s\n", client_pid, + r->in.info->username)); + + /* Parse domain and username */ + + name_map_status = normalize_name_unmap(p->mem_ctx, + r->in.info->username, + &mapped_user); + + /* If the name normalization didn't 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 = discard_const(r->in.info->username); + } + + ok = parse_domain_user(p->mem_ctx, + mapped_user, + &name_namespace, + &name_domain, + &name_user); + if (!ok) { + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + if (mapped_user != r->in.info->username) { + domain_user = talloc_asprintf(talloc_tos(), + "%s%c%s", + name_domain, + *lp_winbind_separator(), + name_user); + if (domain_user == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + r->in.info->username = domain_user; + } + + if (!domain->online) { + result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND; + if (domain->startup) { + /* Logons are very important to users. If we're offline and + we get a request within the first 30 seconds of startup, + try very hard to find a DC and go online. */ + + DEBUG(10,("winbindd_dual_pam_auth: domain: %s offline and auth " + "request in startup mode.\n", domain->name )); + + winbindd_flush_negative_conn_cache(domain); + result = init_dc_connection(domain, false); + } + } + + DEBUG(10,("winbindd_dual_pam_auth: domain: %s last was %s\n", domain->name, domain->online ? "online":"offline")); + + /* Check for Kerberos authentication */ + if (domain->online && (r->in.flags & WBFLAG_PAM_KRB5)) { + result = winbindd_dual_pam_auth_kerberos( + domain, + r->in.info->username, + r->in.info->password, + r->in.info->krb5_cc_type, + uid, + p->mem_ctx, + &validation_level, + &validation, + &krb5ccname); + + /* save for later */ + krb5_result = result; + + if (NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_kerberos succeeded\n")); + goto process_result; + } + + DBG_DEBUG("winbindd_dual_pam_auth_kerberos failed: %s\n", + nt_errstr(result)); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) || + NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) { + DEBUG(10,("winbindd_dual_pam_auth_kerberos setting domain to offline\n")); + set_domain_offline( domain ); + goto cached_logon; + } + + /* there are quite some NT_STATUS errors where there is no + * point in retrying with a samlogon, we explicitly have to take + * care not to increase the bad logon counter on the DC */ + + if (NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_DISABLED) || + NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_EXPIRED) || + NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_LOCKED_OUT) || + NT_STATUS_EQUAL(result, NT_STATUS_INVALID_LOGON_HOURS) || + NT_STATUS_EQUAL(result, NT_STATUS_INVALID_WORKSTATION) || + NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE) || + NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER) || + NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_EXPIRED) || + NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_MUST_CHANGE) || + NT_STATUS_EQUAL(result, NT_STATUS_WRONG_PASSWORD)) { + goto done; + } + + if (r->in.flags & WBFLAG_PAM_FALLBACK_AFTER_KRB5) { + DEBUG(3,("falling back to samlogon\n")); + goto sam_logon; + } else { + goto cached_logon; + } + } + +sam_logon: + /* Check for Samlogon authentication */ + if (domain->online) { + result = winbindd_dual_pam_auth_samlogon( + p->mem_ctx, + domain, + r->in.info->username, + r->in.info->password, + logon_id, + r->in.client_name, + client_pid, + r->in.flags, + remote, + local, + &validation_level, + &validation); + + if (NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_samlogon succeeded\n")); + + switch (validation_level) { + case 3: + base_info = &validation->sam3->base; + break; + case 6: + base_info = &validation->sam6->base; + break; + default: + DBG_ERR("Bad validation level %d\n", + validation_level); + result = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + /* add the Krb5 err if we have one */ + if ( NT_STATUS_EQUAL(krb5_result, NT_STATUS_TIME_DIFFERENCE_AT_DC ) ) { + base_info->user_flags |= LOGON_KRB5_FAIL_CLOCK_SKEW; + } + + goto process_result; + } + + DEBUG(10,("winbindd_dual_pam_auth_samlogon failed: %s\n", + nt_errstr(result))); + + if (NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) || + NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) + { + DEBUG(10,("winbindd_dual_pam_auth_samlogon setting domain to offline\n")); + set_domain_offline( domain ); + goto cached_logon; + } + + if (domain->online) { + /* We're still online - fail. */ + goto done; + } + } + +cached_logon: + /* Check for Cached logons */ + if (!domain->online && (r->in.flags & WBFLAG_PAM_CACHED_LOGIN) && + lp_winbind_offline_logon()) { + result = winbindd_dual_pam_auth_cached(domain, + (r->in.flags & WBFLAG_PAM_KRB5), + r->in.info->username, + r->in.info->password, + r->in.info->krb5_cc_type, + uid, + p->mem_ctx, + &validation_level, + &validation, + &krb5ccname); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_cached failed: %s\n", nt_errstr(result))); + goto done; + } + DEBUG(10,("winbindd_dual_pam_auth_cached succeeded\n")); + } + +process_result: + + if (NT_STATUS_IS_OK(result)) { + struct dom_sid user_sid; + TALLOC_CTX *base_ctx = NULL; + struct netr_SamInfo3 *info3 = NULL; + + switch (validation_level) { + case 3: + base_ctx = validation->sam3; + base_info = &validation->sam3->base; + break; + case 6: + base_ctx = validation->sam6; + base_info = &validation->sam6->base; + break; + default: + DBG_ERR("Bad validation level %d\n", validation_level); + result = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + sid_compose(&user_sid, base_info->domain_sid, base_info->rid); + + if (base_info->full_name.string == NULL) { + struct netr_SamInfo3 *cached_info3; + + cached_info3 = netsamlogon_cache_get(p->mem_ctx, + &user_sid); + if (cached_info3 != NULL && + cached_info3->base.full_name.string != NULL) { + base_info->full_name.string = talloc_strdup( + base_ctx, + cached_info3->base.full_name.string); + if (base_info->full_name.string == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + } else { + + /* this might fail so we don't check the return code */ + wcache_query_user_fullname(domain, + base_ctx, + &user_sid, + &base_info->full_name.string); + } + } + + result = map_validation_to_info3(talloc_tos(), + validation_level, + validation, + &info3); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + wcache_invalidate_samlogon(find_domain_from_name(name_domain), + &user_sid); + netsamlogon_cache_store(name_user, info3); + + /* save name_to_sid info as early as possible (only if + this is our primary domain so we don't invalidate + the cache entry by storing the seq_num for the wrong + domain). */ + if ( domain->primary ) { + cache_name2sid(domain, name_domain, name_user, + SID_NAME_USER, &user_sid); + } + + /* Check if the user is in the right group */ + + result = check_info3_in_group(info3, + r->in.require_membership_of_sid); + if (!NT_STATUS_IS_OK(result)) { + char *s = NDR_PRINT_STRUCT_STRING(p->mem_ctx, + wbint_SidArray, + r->in.require_membership_of_sid); + DBG_NOTICE("User %s is not in the required groups:\n", + r->in.info->username); + DEBUGADD(DBGLVL_NOTICE, ("%s", s)); + DEBUGADD(DBGLVL_NOTICE, + ("Plaintext authentication is rejected\n")); + goto done; + } + + if (!is_allowed_domain(info3->base.logon_domain.string)) { + DBG_NOTICE("Authentication failed for user [%s] " + "from firewalled domain [%s]\n", + info3->base.account_name.string, + info3->base.logon_domain.string); + result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED; + goto done; + } + + r->out.validation = talloc_zero(p->mem_ctx, + struct wbint_Validation); + if (r->out.validation == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + r->out.validation->level = validation_level; + r->out.validation->validation = talloc_steal(r->out.validation, + validation); + r->out.validation->krb5ccname = talloc_steal(r->out.validation, + krb5ccname); + if ((r->in.flags & WBFLAG_PAM_CACHED_LOGIN) + && lp_winbind_offline_logon()) { + + result = winbindd_store_creds(domain, + r->in.info->username, + r->in.info->password, + info3); + } + + result = NT_STATUS_OK; + } + +done: + /* give us a more useful (more correct?) error code */ + if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || + (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) { + result = NT_STATUS_NO_LOGON_SERVERS; + } + + DBG_PREFIX(NT_STATUS_IS_OK(result) ? 5 : 2, + ("Plain-text authentication for user %s returned %s" + " (PAM: %d)\n", + r->in.info->username, + nt_errstr(result), + nt_status_to_pam(result))); + + /* + * Log the winbind pam authentication, the logon_id will tie this to + * any of the logons invoked from this request. + */ + + log_authentication( + p->mem_ctx, + domain, + r->in.client_name, + client_pid, + validation_level, + validation, + start_time, + logon_id, + "PAM_AUTH", + name_user, + name_domain, + NULL, + data_blob_null, + data_blob_null, + remote, + local, + result); + + if (NT_STATUS_IS_OK(result)) { + gpupdate_user_init(r->in.info->username); + } + + return result; +} + +NTSTATUS winbind_dual_SamLogon(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + bool interactive, + uint32_t logon_parameters, + const char *name_user, + const char *name_domain, + const char *workstation, + const uint64_t logon_id, + const char* client_name, + const int client_pid, + DATA_BLOB chal_blob, + DATA_BLOB lm_response, + DATA_BLOB nt_response, + const struct tsocket_address *remote, + const struct tsocket_address *local, + uint8_t *authoritative, + bool skip_sam, + uint32_t *flags, + uint16_t *_validation_level, + union netr_Validation **_validation) +{ + uint16_t validation_level = 0; + union netr_Validation *validation = NULL; + NTSTATUS result; + + /* + * We check against domain->name instead of + * name_domain, as find_auth_domain() -> + * find_domain_from_name_noinit() already decided + * that we are in a child for the correct domain. + * + * name_domain can also be lp_realm() + * we need to check against domain->name. + */ + if (!skip_sam && strequal(domain->name, get_global_sam_name())) { + struct netr_SamInfo3 *info3 = NULL; + + result = winbindd_dual_auth_passdb( + talloc_tos(), + logon_parameters, + name_domain, name_user, + logon_id, + client_name, + client_pid, + &chal_blob, &lm_response, &nt_response, + remote, + local, + interactive, + authoritative, + &info3); + if (NT_STATUS_IS_OK(result)) { + result = map_info3_to_validation(mem_ctx, + info3, + &validation_level, + &validation); + TALLOC_FREE(info3); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + } + + /* + * We need to try the remote NETLOGON server if this is + * not authoritative. + */ + if (*authoritative != 0) { + *flags = 0; + goto process_result; + } + } + + result = winbind_samlogon_retry_loop(domain, + mem_ctx, + logon_parameters, + name_user, + NULL, /* password */ + name_domain, + /* Bug #3248 - found by Stefan Burkei. */ + workstation, /* We carefully set this above so use it... */ + logon_id, + false, /* plaintext_given */ + chal_blob, + lm_response, + nt_response, + interactive, + authoritative, + flags, + &validation_level, + &validation); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + +process_result: + + if (NT_STATUS_IS_OK(result)) { + struct dom_sid user_sid; + TALLOC_CTX *base_ctx = NULL; + struct netr_SamBaseInfo *base_info = NULL; + struct netr_SamInfo3 *info3 = NULL; + + switch (validation_level) { + case 3: + base_ctx = validation->sam3; + base_info = &validation->sam3->base; + break; + case 6: + base_ctx = validation->sam6; + base_info = &validation->sam6->base; + break; + default: + result = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + sid_compose(&user_sid, base_info->domain_sid, base_info->rid); + + if (base_info->full_name.string == NULL) { + struct netr_SamInfo3 *cached_info3; + + cached_info3 = netsamlogon_cache_get(mem_ctx, + &user_sid); + if (cached_info3 != NULL && + cached_info3->base.full_name.string != NULL) + { + base_info->full_name.string = talloc_strdup( + base_ctx, + cached_info3->base.full_name.string); + } else { + + /* this might fail so we don't check the return code */ + wcache_query_user_fullname(domain, + base_ctx, + &user_sid, + &base_info->full_name.string); + } + } + + result = map_validation_to_info3(talloc_tos(), + validation_level, + validation, + &info3); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + wcache_invalidate_samlogon(find_domain_from_name(name_domain), + &user_sid); + netsamlogon_cache_store(name_user, info3); + TALLOC_FREE(info3); + } + +done: + + /* give us a more useful (more correct?) error code */ + if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || + (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) { + result = NT_STATUS_NO_LOGON_SERVERS; + } + + DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, + ("NTLM CRAP authentication for user [%s]\\[%s] returned %s\n", + name_domain, + name_user, + nt_errstr(result))); + + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + *_validation_level = validation_level; + *_validation = validation; + return NT_STATUS_OK; +} + +NTSTATUS _wbint_PamAuthCrap(struct pipes_struct *p, struct wbint_PamAuthCrap *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS result; + uint64_t logon_id = 0; + uint8_t authoritative = 1; + uint32_t flags = 0; + uint16_t validation_level = UINT16_MAX; + union netr_Validation *validation = NULL; + const struct timeval start_time = timeval_current(); + const struct tsocket_address *remote = NULL; + const struct tsocket_address *local = NULL; + struct netr_SamInfo3 *info3 = NULL; + pid_t client_pid; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + /* Cut client_pid to 32bit */ + client_pid = r->in.client_pid; + if ((uint64_t)client_pid != r->in.client_pid) { + DBG_DEBUG("pid out of range\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + logon_id = generate_random_u64(); + remote = dcesrv_connection_get_remote_address(p->dce_call->conn); + local = dcesrv_connection_get_local_address(p->dce_call->conn); + + DBG_NOTICE("[%"PRIu32"]: pam auth crap domain: %s user: %s\n", + client_pid, r->in.domain, r->in.user); + + result = winbind_dual_SamLogon(domain, + p->mem_ctx, + false, /* interactive */ + r->in.logon_parameters, + r->in.user, + r->in.domain, + r->in.workstation, + logon_id, + r->in.client_name, + client_pid, + r->in.chal, + r->in.lm_resp, + r->in.nt_resp, + remote, + local, + &authoritative, + false, + &flags, + &validation_level, + &validation); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + result = map_validation_to_info3(p->mem_ctx, + validation_level, + validation, + &info3); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + /* Check if the user is in the right group */ + result = check_info3_in_group(info3, r->in.require_membership_of_sid); + if (!NT_STATUS_IS_OK(result)) { + char *s = NDR_PRINT_STRUCT_STRING(p->mem_ctx, + wbint_SidArray, + r->in.require_membership_of_sid); + DBG_NOTICE("User %s is not in the required groups:\n", + r->in.user); + DEBUGADD(DBGLVL_NOTICE, ("%s", s)); + DEBUGADD(DBGLVL_NOTICE, + ("CRAP authentication is rejected\n")); + goto done; + } + + if (!is_allowed_domain(info3->base.logon_domain.string)) { + DBG_NOTICE("Authentication failed for user [%s] " + "from firewalled domain [%s]\n", + info3->base.account_name.string, + info3->base.logon_domain.string); + result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED; + goto done; + } + + r->out.validation = talloc_zero(p->mem_ctx, + struct wbint_PamAuthCrapValidation); + if (r->out.validation == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + r->out.validation->level = validation_level; + r->out.validation->validation = talloc_move(r->out.validation, + &validation); +done: + + if (r->in.flags & WBFLAG_PAM_NT_STATUS_SQUASH) { + result = nt_status_squash(result); + } + + *r->out.authoritative = authoritative; + + /* + * Log the winbind pam authentication, the logon_id will tie this to + * any of the logons invoked from this request. + */ + log_authentication( + p->mem_ctx, + domain, + r->in.client_name, + client_pid, + r->out.validation->level, + r->out.validation->validation, + start_time, + logon_id, + "NTLM_AUTH", + r->in.user, + r->in.domain, + r->in.workstation, + r->in.lm_resp, + r->in.nt_resp, + remote, + local, + result); + + return result; +} + +NTSTATUS _wbint_PamAuthChangePassword(struct pipes_struct *p, + struct wbint_PamAuthChangePassword *r) +{ + struct winbindd_domain *contact_domain = wb_child_domain(); + struct policy_handle dom_pol; + struct rpc_pipe_client *cli = NULL; + bool got_info = false; + struct samr_DomInfo1 *info = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + char *namespace = NULL; + char *domain = NULL; + char *user = NULL; + struct dcerpc_binding_handle *b = NULL; + bool ok; + pid_t client_pid; + + ZERO_STRUCT(dom_pol); + + if (contact_domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + /* Cut client_pid to 32bit */ + client_pid = r->in.client_pid; + if ((uint64_t)client_pid != r->in.client_pid) { + DBG_DEBUG("pid out of range\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + DBG_NOTICE("[%"PRIu32"]: dual pam chauthtok %s\n", + client_pid, r->in.user); + + ok = parse_domain_user(p->mem_ctx, + r->in.user, + &namespace, + &domain, + &user); + if (!ok) { + goto done; + } + + if (!is_allowed_domain(domain)) { + DBG_NOTICE("Authentication failed for user [%s] " + "from firewalled domain [%s]\n", + user, domain); + result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED; + goto done; + } + + /* Initialize reject reason */ + *r->out.reject_reason = Undefined; + + /* Get sam handle */ + + result = cm_connect_sam(contact_domain, + p->mem_ctx, + true, + &cli, + &dom_pol); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("could not get SAM handle on DC for %s\n", domain)); + goto done; + } + + b = cli->binding_handle; + + status = dcerpc_samr_chgpasswd_user4(cli->binding_handle, + p->mem_ctx, + cli->srv_name_slash, + user, + r->in.old_password, + r->in.new_password, + &result); + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) { + /* Password successfully changed. */ + goto done; + } + if (!NT_STATUS_IS_OK(status)) { + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE) || + NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) || + NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) { + /* DO NOT FALLBACK TO RC4 */ + if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED) { + result = NT_STATUS_STRONG_CRYPTO_NOT_SUPPORTED; + goto process_result; + } + } + } else { + /* Password change was unsuccessful. */ + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + } + + result = rpccli_samr_chgpasswd_user3(cli, + p->mem_ctx, + user, + r->in.new_password, + r->in.old_password, + &info, + &reject); + + /* Windows 2003 returns NT_STATUS_PASSWORD_RESTRICTION */ + + if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION) ) { + + *r->out.dominfo = talloc_steal(p->mem_ctx, info); + *r->out.reject_reason = reject->extendedFailureReason; + + got_info = true; + } + + /* atm the pidl generated rpccli_samr_ChangePasswordUser3 function will + * return with NT_STATUS_BUFFER_TOO_SMALL for w2k dcs as w2k just + * returns with 4byte error code (NT_STATUS_NOT_SUPPORTED) which is too + * short to comply with the samr_ChangePasswordUser3 idl - gd */ + + /* only fallback when the chgpasswd_user3 call is not supported */ + if (NT_STATUS_EQUAL(result, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE) || + NT_STATUS_EQUAL(result, NT_STATUS_NOT_SUPPORTED) || + NT_STATUS_EQUAL(result, NT_STATUS_BUFFER_TOO_SMALL) || + NT_STATUS_EQUAL(result, NT_STATUS_NOT_IMPLEMENTED)) { + + DEBUG(10,("Password change with chgpasswd_user3 failed with: %s, retrying chgpasswd_user2\n", + nt_errstr(result))); + + result = rpccli_samr_chgpasswd_user2(cli, + p->mem_ctx, + user, + r->in.new_password, + r->in.old_password); + + /* Windows 2000 returns NT_STATUS_ACCOUNT_RESTRICTION. + Map to the same status code as Windows 2003. */ + + if ( NT_STATUS_EQUAL(NT_STATUS_ACCOUNT_RESTRICTION, result ) ) { + result = NT_STATUS_PASSWORD_RESTRICTION; + } + } + +done: + + if (NT_STATUS_IS_OK(result) + && (r->in.flags & WBFLAG_PAM_CACHED_LOGIN) + && lp_winbind_offline_logon()) { + result = winbindd_update_creds_by_name(contact_domain, user, + r->in.new_password); + /* Again, this happens when we login from gdm or xdm + * and the password expires, *BUT* cached credentials + * don't exist. winbindd_update_creds_by_name() + * returns NT_STATUS_NO_SUCH_USER. + * This is not a failure. + * --- BoYang + * */ + if (NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER)) { + result = NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10, ("Failed to store creds: %s\n", + nt_errstr(result))); + goto process_result; + } + } + + if (!NT_STATUS_IS_OK(result) && !got_info && contact_domain) { + + NTSTATUS policy_ret; + + policy_ret = get_password_policy(contact_domain, + p->mem_ctx, + &info); + + /* failure of this is non critical, it will just provide no + * additional information to the client why the change has + * failed - Guenther */ + + if (!NT_STATUS_IS_OK(policy_ret)) { + DEBUG(10,("Failed to get password policies: %s\n", nt_errstr(policy_ret))); + goto process_result; + } + + *r->out.dominfo = talloc_steal(p->mem_ctx, info); + } + +process_result: + + if (strequal(contact_domain->name, get_global_sam_name())) { + /* FIXME: internal rpc pipe does not cache handles yet */ + if (b) { + if (is_valid_policy_hnd(&dom_pol)) { + NTSTATUS _result; + dcerpc_samr_Close(b, + p->mem_ctx, + &dom_pol, + &_result); + } + TALLOC_FREE(cli); + } + } + + DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, + ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n", + domain, + user, + nt_errstr(result), + nt_status_to_pam(result))); + + return result; +} + +NTSTATUS _wbint_PamLogOff(struct pipes_struct *p, struct wbint_PamLogOff *r) +{ + struct winbindd_domain *domain = wb_child_domain(); + NTSTATUS result = NT_STATUS_NOT_SUPPORTED; + pid_t client_pid; + uid_t user_uid; + + if (domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + /* Cut client_pid to 32bit */ + client_pid = r->in.client_pid; + if ((uint64_t)client_pid != r->in.client_pid) { + DBG_DEBUG("pid out of range\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + /* Cut uid to 32bit */ + user_uid = r->in.uid; + if ((uint64_t)user_uid != r->in.uid) { + DBG_DEBUG("uid out of range\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + DBG_NOTICE("[%"PRIu32"]: pam dual logoff %s\n", client_pid, r->in.user); + + if (!(r->in.flags & WBFLAG_PAM_KRB5)) { + result = NT_STATUS_OK; + goto process_result; + } + + if ((r->in.krb5ccname == NULL) || (strlen(r->in.krb5ccname) == 0)) { + result = NT_STATUS_OK; + goto process_result; + } + +#ifdef HAVE_KRB5 + + if (user_uid == (uid_t)-1) { + DBG_DEBUG("Invalid uid for user '%s'\n", r->in.user); + goto process_result; + } + + /* what we need here is to find the corresponding krb5 ccache name *we* + * created for a given username and destroy it */ + + if (!ccache_entry_exists(r->in.user)) { + result = NT_STATUS_OK; + DBG_DEBUG("No entry found for user '%s'.\n", r->in.user); + goto process_result; + } + + if (!ccache_entry_identical(r->in.user, user_uid, r->in.krb5ccname)) { + DBG_DEBUG("Cached entry differs for user '%s'\n", r->in.user); + goto process_result; + } + + result = remove_ccache(r->in.user); + if (!NT_STATUS_IS_OK(result)) { + DBG_DEBUG("Failed to remove ccache for user '%s': %s\n", + r->in.user, nt_errstr(result)); + goto process_result; + } + + /* + * Remove any mlock'ed memory creds in the child + * we might be using for krb5 ticket renewal. + */ + + winbindd_delete_memory_creds(r->in.user); + +#else + result = NT_STATUS_NOT_SUPPORTED; +#endif + +process_result: + + return result; +} + +/* Change user password with auth crap*/ + +NTSTATUS _wbint_PamAuthCrapChangePassword(struct pipes_struct *p, + struct wbint_PamAuthCrapChangePassword *r) +{ + NTSTATUS result; + char *namespace = NULL; + char *domain = NULL; + char *user = NULL; + struct policy_handle dom_pol; + struct winbindd_domain *contact_domain = wb_child_domain(); + struct rpc_pipe_client *cli = NULL; + struct dcerpc_binding_handle *b = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + pid_t client_pid; + + ZERO_STRUCT(dom_pol); + + if (contact_domain == NULL) { + return NT_STATUS_REQUEST_NOT_ACCEPTED; + } + + /* Cut client_pid to 32bit */ + client_pid = r->in.client_pid; + if ((uint64_t)client_pid != r->in.client_pid) { + DBG_DEBUG("pid out of range\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + DBG_NOTICE("[%"PRIu32"]: pam change pswd auth crap domain: %s " + "user: %s\n", client_pid, r->in.domain, r->in.user); + + if (lp_winbind_offline_logon()) { + DEBUG(0,("Refusing password change as winbind offline logons are enabled. ")); + DEBUGADD(0,("Changing passwords here would risk inconsistent logons\n")); + result = NT_STATUS_ACCESS_DENIED; + goto done; + } + + if (r->in.domain != NULL && strlen(r->in.domain) > 0) { + user = talloc_strdup(frame, ""); + namespace = talloc_strdup(frame, ""); + domain = talloc_strdup(frame, r->in.domain); + if (domain == NULL || user == NULL || namespace == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + + } else { + bool ok; + + ok = parse_domain_user(frame, + r->in.user, + &namespace, + &domain, + &user); + if (!ok) { + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + if (strlen(domain) == 0) { + DBG_NOTICE("no domain specified with username (%s) - " + "failing auth\n", r->in.user); + result = NT_STATUS_NO_SUCH_USER; + goto done; + } + } + + if (!*domain && lp_winbind_use_default_domain()) { + TALLOC_FREE(domain); + domain = talloc_strdup(frame, lp_workgroup()); + if (domain == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + } + + if (!is_allowed_domain(domain)) { + DBG_NOTICE("Authentication failed for user [%s] " + "from firewalled domain [%s]\n", + r->in.user, + domain); + result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED; + goto done; + } + + if(!*user) { + TALLOC_FREE(user); + user = talloc_strdup(frame, r->in.user); + if (user == NULL) { + result = NT_STATUS_NO_SUCH_USER; + goto done; + } + } + + /* Get sam handle */ + + result = cm_connect_sam(contact_domain, + p->mem_ctx, + true, + &cli, + &dom_pol); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("could not get SAM handle on DC for %s\n", domain)); + goto done; + } + + b = cli->binding_handle; + + result = rpccli_samr_chng_pswd_auth_crap(cli, + p->mem_ctx, + user, + r->in.new_nt_pswd, + r->in.old_nt_hash_enc, + r->in.new_lm_pswd, + r->in.old_lm_hash_enc); + + done: + + if (strequal(contact_domain->name, get_global_sam_name())) { + /* FIXME: internal rpc pipe does not cache handles yet */ + if (b) { + if (is_valid_policy_hnd(&dom_pol)) { + NTSTATUS _result; + dcerpc_samr_Close(b, + p->mem_ctx, + &dom_pol, + &_result); + } + TALLOC_FREE(cli); + } + } + + DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, + ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n", + domain, user, + nt_errstr(result), + nt_status_to_pam(result))); + TALLOC_FREE(frame); + return result; +} + +#ifdef HAVE_KRB5 +static NTSTATUS extract_pac_vrfy_sigs(TALLOC_CTX *mem_ctx, DATA_BLOB pac_blob, + struct PAC_DATA **p_pac_data) +{ + krb5_context krbctx = NULL; + krb5_error_code k5ret; + krb5_keytab keytab; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + ZERO_STRUCT(entry); + ZERO_STRUCT(cursor); + + k5ret = smb_krb5_init_context_common(&krbctx); + if (k5ret) { + DBG_ERR("kerberos init context failed (%s)\n", + error_message(k5ret)); + status = krb5_to_nt_status(k5ret); + goto out; + } + + k5ret = gse_krb5_get_server_keytab(krbctx, &keytab); + if (k5ret) { + DEBUG(1, ("Failed to get keytab: %s\n", + error_message(k5ret))); + status = krb5_to_nt_status(k5ret); + goto out_free; + } + + k5ret = krb5_kt_start_seq_get(krbctx, keytab, &cursor); + if (k5ret) { + DEBUG(1, ("Failed to start seq: %s\n", + error_message(k5ret))); + status = krb5_to_nt_status(k5ret); + goto out_keytab; + } + + k5ret = krb5_kt_next_entry(krbctx, keytab, &entry, &cursor); + while (k5ret == 0) { + status = kerberos_decode_pac(mem_ctx, + pac_blob, + krbctx, + NULL, /* krbtgt_keyblock */ + KRB5_KT_KEY(&entry), /* service_keyblock */ + NULL, /* client_principal */ + 0, /* tgs_authtime */ + p_pac_data); + if (NT_STATUS_IS_OK(status)) { + break; + } + k5ret = smb_krb5_kt_free_entry(krbctx, &entry); + k5ret = krb5_kt_next_entry(krbctx, keytab, &entry, &cursor); + } + + k5ret = krb5_kt_end_seq_get(krbctx, keytab, &cursor); + if (k5ret) { + DEBUG(1, ("Failed to end seq: %s\n", + error_message(k5ret))); + } +out_keytab: + k5ret = krb5_kt_close(krbctx, keytab); + if (k5ret) { + DEBUG(1, ("Failed to close keytab: %s\n", + error_message(k5ret))); + } +out_free: + krb5_free_context(krbctx); +out: + return status; +} + +NTSTATUS winbindd_pam_auth_pac_verify(struct winbindd_cli_state *state, + TALLOC_CTX *mem_ctx, + bool *p_is_trusted, + uint16_t *p_validation_level, + union netr_Validation **p_validation) +{ + struct winbindd_request *req = state->request; + DATA_BLOB pac_blob; + struct PAC_DATA *pac_data = NULL; + struct PAC_LOGON_INFO *logon_info = NULL; + struct PAC_UPN_DNS_INFO *upn_dns_info = NULL; + struct netr_SamInfo6 *info6 = NULL; + uint16_t validation_level = 0; + union netr_Validation *validation = NULL; + struct netr_SamInfo3 *info3_copy = NULL; + NTSTATUS result; + bool is_trusted = false; + uint32_t i; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *p_is_trusted = false; + *p_validation_level = 0; + *p_validation = NULL; + + pac_blob = data_blob_const(req->extra_data.data, req->extra_len); + result = extract_pac_vrfy_sigs(tmp_ctx, pac_blob, &pac_data); + if (NT_STATUS_IS_OK(result)) { + is_trusted = true; + } + if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) { + /* Try without signature verification */ + result = kerberos_decode_pac(tmp_ctx, + pac_blob, + NULL, /* krb5_context */ + NULL, /* krbtgt_keyblock */ + NULL, /* service_keyblock */ + NULL, /* client_principal */ + 0, /* tgs_authtime */ + &pac_data); + } + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("Error during PAC signature verification: %s\n", + nt_errstr(result))); + goto out; + } + + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type == PAC_TYPE_LOGON_INFO) { + logon_info = pac_data->buffers[i].info->logon_info.info; + continue; + } + if (pac_data->buffers[i].type == PAC_TYPE_UPN_DNS_INFO) { + upn_dns_info = &pac_data->buffers[i].info->upn_dns_info; + continue; + } + } + + result = create_info6_from_pac(tmp_ctx, + logon_info, + upn_dns_info, + &info6); + if (!NT_STATUS_IS_OK(result)) { + goto out; + } + + if (!is_allowed_domain(info6->base.logon_domain.string)) { + DBG_NOTICE("Authentication failed for user [%s] " + "from firewalled domain [%s]\n", + info6->base.account_name.string, + info6->base.logon_domain.string); + result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED; + goto out; + } + + result = map_info6_to_validation(tmp_ctx, + info6, + &validation_level, + &validation); + if (!NT_STATUS_IS_OK(result)) { + goto out; + } + + result = map_validation_to_info3(tmp_ctx, + validation_level, + validation, + &info3_copy); + if (!NT_STATUS_IS_OK(result)) { + goto out; + } + + if (is_trusted) { + /* + * Signature verification succeeded, we can + * trust the PAC and prime the netsamlogon + * and name2sid caches. DO NOT DO THIS + * in the signature verification failed + * code path. + */ + struct winbindd_domain *domain = NULL; + + netsamlogon_cache_store(NULL, info3_copy); + + /* + * We're in the parent here, so find the child + * pointer from the PAC domain name. + */ + domain = find_lookup_domain_from_name( + info3_copy->base.logon_domain.string); + if (domain && domain->primary ) { + struct dom_sid user_sid; + struct dom_sid_buf buf; + + sid_compose(&user_sid, + info3_copy->base.domain_sid, + info3_copy->base.rid); + + cache_name2sid_trusted(domain, + info3_copy->base.logon_domain.string, + info3_copy->base.account_name.string, + SID_NAME_USER, + &user_sid); + + DBG_INFO("PAC for user %s\\%s SID %s primed cache\n", + info3_copy->base.logon_domain.string, + info3_copy->base.account_name.string, + dom_sid_str_buf(&user_sid, &buf)); + } + } + + *p_is_trusted = is_trusted; + *p_validation_level = validation_level; + *p_validation = talloc_move(mem_ctx, &validation); + + result = NT_STATUS_OK; +out: + TALLOC_FREE(tmp_ctx); + return result; +} +#else /* HAVE_KRB5 */ +NTSTATUS winbindd_pam_auth_pac_verify(struct winbindd_cli_state *state, + TALLOC_CTX *mem_ctx, + bool *p_is_trusted, + uint16_t *p_validation_level, + union netr_Validation **p_validation); +{ + + *p_is_trusted = false; + *p_validation_level = 0; + *p_validation = NULL; + return NT_STATUS_NO_SUCH_USER; +} +#endif /* HAVE_KRB5 */ diff --git a/source3/winbindd/winbindd_pam_auth.c b/source3/winbindd/winbindd_pam_auth.c new file mode 100644 index 0000000..431da09 --- /dev/null +++ b/source3/winbindd/winbindd_pam_auth.c @@ -0,0 +1,291 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_PAM_AUTH + Copyright (C) Volker Lendecke 2010 + + 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.h" +#include "libcli/security/dom_sid.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +static NTSTATUS fake_password_policy(struct winbindd_response *r, + uint16_t validation_level, + union netr_Validation *validation) +{ + const struct netr_SamBaseInfo *bi = NULL; + NTTIME min_password_age; + NTTIME max_password_age; + + switch (validation_level) { + case 3: + bi = &validation->sam3->base; + break; + case 6: + bi = &validation->sam6->base; + break; + default: + return NT_STATUS_INTERNAL_ERROR; + } + + if (bi->allow_password_change > bi->last_password_change) { + min_password_age = bi->allow_password_change - + bi->last_password_change; + } else { + min_password_age = 0; + } + + if (bi->force_password_change > bi->last_password_change) { + max_password_age = bi->force_password_change - + bi->last_password_change; + } else { + max_password_age = 0; + } + + r->data.auth.policy.min_length_password = 0; + r->data.auth.policy.password_history = 0; + r->data.auth.policy.password_properties = 0; + r->data.auth.policy.expire = + nt_time_to_unix_abs(&max_password_age); + r->data.auth.policy.min_passwordage = + nt_time_to_unix_abs(&min_password_age); + + return NT_STATUS_OK; +} + +struct winbindd_pam_auth_state { + struct wbint_PamAuth *r; + char *name_namespace; + char *name_domain; + char *name_user; +}; + +static void winbindd_pam_auth_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_pam_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_pam_auth_state *state; + struct winbindd_domain *domain; + char *mapped = NULL; + char *auth_user = NULL; + NTSTATUS status; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_pam_auth_state); + if (req == NULL) { + return NULL; + } + + D_NOTICE("[%s (%u)] Winbind external command PAM_AUTH start.\n" + "Authenticating user '%s'.\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.auth.user); + + if (!check_request_flags(request->flags)) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + /* Parse domain and username */ + + status = normalize_name_unmap(state, request->data.auth.user, &mapped); + + /* If the name normalization changed something, copy it over the given + name */ + + if (NT_STATUS_IS_OK(status) + || NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) { + fstrcpy(request->data.auth.user, mapped); + } + + auth_user = request->data.auth.user; + ok = canonicalize_username(state, + &auth_user, + &state->name_namespace, + &state->name_domain, + &state->name_user); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return tevent_req_post(req, ev); + } + + fstrcpy(request->data.auth.user, auth_user); + + domain = find_auth_domain(request->flags, state->name_namespace); + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return tevent_req_post(req, ev); + } + + state->r = talloc_zero(state, struct wbint_PamAuth); + if (tevent_req_nomem(state->r, req)) { + return tevent_req_post(req, ev); + } + + state->r->in.client_name = talloc_strdup( + state->r, request->client_name); + if (tevent_req_nomem(state->r, req)) { + return tevent_req_post(req, ev); + } + + state->r->in.client_pid = request->pid; + state->r->in.flags = request->flags; + + state->r->in.info = talloc_zero(state->r, struct wbint_AuthUserInfo); + if (tevent_req_nomem(state->r, req)) { + return tevent_req_post(req, ev); + } + + state->r->in.info->krb5_cc_type = talloc_strdup( + state->r, request->data.auth.krb5_cc_type); + if (tevent_req_nomem(state->r, req)) { + return tevent_req_post(req, ev); + } + + state->r->in.info->password = talloc_strdup( + state->r, request->data.auth.pass); + if (tevent_req_nomem(state->r, req)) { + return tevent_req_post(req, ev); + } + + state->r->in.info->username = talloc_strdup( + state->r, request->data.auth.user); + if (tevent_req_nomem(state->r, req)) { + return tevent_req_post(req, ev); + } + + state->r->in.info->uid = request->data.auth.uid; + + status = extra_data_to_sid_array( + request->data.auth.require_membership_of_sid, + state->r, + &state->r->in.require_membership_of_sid); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_PamAuth_r_send(state, + global_event_context(), + dom_child_handle(domain), + state->r); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_pam_auth_done, req); + return req; +} + +static void winbindd_pam_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_pam_auth_state *state = tevent_req_data( + req, struct winbindd_pam_auth_state); + NTSTATUS status; + + status = dcerpc_wbint_PamAuth_r_recv(subreq, state); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (tevent_req_nterror(req, state->r->out.result)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS winbindd_pam_auth_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_pam_auth_state *state = tevent_req_data( + req, struct winbindd_pam_auth_state); + NTSTATUS status; + + D_NOTICE("Winbind external command PAM_AUTH end.\n"); + if (tevent_req_is_nterror(req, &status)) { + set_auth_errors(response, status); + return status; + } + + response->result = WINBINDD_PENDING; + + status = append_auth_data(response, + response, + state->r->in.flags, + state->r->out.validation->level, + state->r->out.validation->validation, + state->name_domain, + state->name_user); + fstrcpy(response->data.auth.krb5ccname, + state->r->out.validation->krb5ccname); + + if (state->r->in.flags & WBFLAG_PAM_INFO3_TEXT) { + bool ok; + + ok = add_trusted_domain_from_auth( + state->r->out.validation->level, + &response->data.auth.info3, + &response->data.auth.info6); + if (!ok) { + DBG_ERR("add_trusted_domain_from_auth failed\n"); + set_auth_errors(response, NT_STATUS_LOGON_FAILURE); + return NT_STATUS_LOGON_FAILURE; + } + } + + if (state->r->in.flags & WBFLAG_PAM_CACHED_LOGIN) { + + /* Store in-memory creds for single-signon using ntlm_auth. */ + + status = winbindd_add_memory_creds( + state->r->in.info->username, + state->r->in.info->uid, + state->r->in.info->password); + D_DEBUG("winbindd_add_memory_creds returned: %s\n", + nt_errstr(status)); + } + + if (state->r->in.flags & WBFLAG_PAM_GET_PWD_POLICY) { + /* + * WBFLAG_PAM_GET_PWD_POLICY is not used within + * any Samba caller anymore. + * + * We just fake this based on the effective values + * for the user, for legacy callers. + */ + status = fake_password_policy(response, + state->r->out.validation->level, + state->r->out.validation->validation); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to fake password policy: %s\n", + nt_errstr(status)); + set_auth_errors(response, status); + return status; + } + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_pam_auth_crap.c b/source3/winbindd/winbindd_pam_auth_crap.c new file mode 100644 index 0000000..e6a32c7 --- /dev/null +++ b/source3/winbindd/winbindd_pam_auth_crap.c @@ -0,0 +1,285 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_PAM_AUTH_CRAP + Copyright (C) Volker Lendecke 2010 + + 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.h" +#include "rpc_client/util_netlogon.h" +#include "libcli/security/dom_sid.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_pam_auth_crap_state { + uint8_t authoritative; + uint32_t flags; + bool pac_is_trusted; + char *domain; + char *user; + struct wbint_PamAuthCrapValidation validation; + NTSTATUS result; +}; + +static void winbindd_pam_auth_crap_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_pam_auth_crap_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_pam_auth_crap_state *state; + struct winbindd_domain *domain; + const char *auth_domain = NULL; + DATA_BLOB lm_resp = data_blob_null; + DATA_BLOB nt_resp = data_blob_null; + DATA_BLOB chal = data_blob_null; + struct wbint_SidArray *require_membership_of_sid = NULL; + NTSTATUS status; + bool lmlength_ok = false; + bool ntlength_ok = false; + bool pwlength_ok = false; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_pam_auth_crap_state); + if (req == NULL) { + return NULL; + } + state->authoritative = 1; + state->flags = request->flags; + + if (state->flags & WBFLAG_PAM_AUTH_PAC) { + state->result = winbindd_pam_auth_pac_verify(cli, + state, + &state->pac_is_trusted, + &state->validation.level, + &state->validation.validation); + if (tevent_req_nterror(req, state->result)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + /* Ensure null termination */ + request->data.auth_crap.user[ + sizeof(request->data.auth_crap.user)-1] = '\0'; + request->data.auth_crap.domain[ + sizeof(request->data.auth_crap.domain)-1] = '\0'; + request->data.auth_crap.workstation[ + sizeof(request->data.auth_crap.workstation)-1] = '\0'; + + DBG_NOTICE("[%5lu]: pam auth crap domain: [%s] user: [%s] " + "workstation: [%s]\n", + (unsigned long)cli->pid, + request->data.auth_crap.domain, + request->data.auth_crap.user, + request->data.auth_crap.workstation); + + if (!check_request_flags(request->flags)) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + auth_domain = request->data.auth_crap.domain; + if (auth_domain[0] == '\0') { + auth_domain = lp_workgroup(); + } + + domain = find_auth_domain(request->flags, auth_domain); + if (domain == NULL) { + /* + * We don't know the domain so + * we're not authoritative + */ + state->authoritative = 0; + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return tevent_req_post(req, ev); + } + + if (request->data.auth_crap.workstation[0] == '\0') { + fstrcpy(request->data.auth_crap.workstation, lp_netbios_name()); + } + + lmlength_ok = (request->data.auth_crap.lm_resp_len <= + sizeof(request->data.auth_crap.lm_resp)); + + ntlength_ok = (request->data.auth_crap.nt_resp_len <= + sizeof(request->data.auth_crap.nt_resp)); + + ntlength_ok |= + ((request->flags & WBFLAG_BIG_NTLMV2_BLOB) && + (request->extra_len == request->data.auth_crap.nt_resp_len)); + + pwlength_ok = lmlength_ok && ntlength_ok; + + if (!pwlength_ok) { + DBG_ERR("Invalid password length %u/%u\n", + request->data.auth_crap.lm_resp_len, + request->data.auth_crap.nt_resp_len); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + state->domain = talloc_strdup(state, request->data.auth_crap.domain); + if (tevent_req_nomem(state->domain, req)) { + return tevent_req_post(req, ev); + } + + state->user = talloc_strdup(state, request->data.auth_crap.user); + if (tevent_req_nomem(state->user, req)) { + return tevent_req_post(req, ev); + } + + status = extra_data_to_sid_array( + request->data.auth_crap.require_membership_of_sid, + state, + &require_membership_of_sid); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + lm_resp = data_blob_talloc(state, + request->data.auth_crap.lm_resp, + request->data.auth_crap.lm_resp_len); + if (tevent_req_nomem(lm_resp.data, req)) { + return tevent_req_post(req, ev); + } + + if (request->flags & WBFLAG_BIG_NTLMV2_BLOB) { + nt_resp = data_blob_talloc(state, + request->extra_data.data, + request->data.auth_crap.nt_resp_len); + } else { + nt_resp = data_blob_talloc(state, + request->data.auth_crap.nt_resp, + request->data.auth_crap.nt_resp_len); + } + if (tevent_req_nomem(nt_resp.data, req)) { + return tevent_req_post(req, ev); + } + + chal = data_blob_talloc(state, + request->data.auth_crap.chal, + 8); + if (tevent_req_nomem(chal.data, req)) { + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_PamAuthCrap_send(state, + global_event_context(), + dom_child_handle(domain), + request->client_name, + request->pid, + request->flags, + request->data.auth_crap.user, + request->data.auth_crap.domain, + request->data.auth_crap.workstation, + lm_resp, + nt_resp, + chal, + request->data.auth_crap.logon_parameters, + require_membership_of_sid, + &state->authoritative, + &state->validation); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_pam_auth_crap_done, req); + return req; +} + +static void winbindd_pam_auth_crap_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_pam_auth_crap_state *state = tevent_req_data( + req, struct winbindd_pam_auth_crap_state); + NTSTATUS status; + + status = dcerpc_wbint_PamAuthCrap_recv(subreq, state, &state->result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS winbindd_pam_auth_crap_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_pam_auth_crap_state *state = tevent_req_data( + req, struct winbindd_pam_auth_crap_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + goto out; + } + + if (NT_STATUS_IS_ERR(state->result)) { + status = state->result; + goto out; + } + + status = append_auth_data(response, + response, + state->flags, + state->validation.level, + state->validation.validation, + state->domain, + state->user); + if (NT_STATUS_IS_ERR(status)) { + goto out; + } + + if (state->flags & WBFLAG_PAM_AUTH_PAC && !state->pac_is_trusted) { + /* + * Clear the flag just in state to do no add the domain + * from auth below. + */ + state->flags &= ~WBFLAG_PAM_INFO3_TEXT; + } + + if (state->flags & WBFLAG_PAM_INFO3_TEXT) { + bool ok; + + ok = add_trusted_domain_from_auth( + response->data.auth.validation_level, + &response->data.auth.info3, + &response->data.auth.info6); + if (!ok) { + status = NT_STATUS_LOGON_FAILURE; + DBG_ERR("add_trusted_domain_from_auth failed\n"); + set_auth_errors(response, status); + response->data.auth.authoritative = + state->authoritative; + return status; + } + } + + status = NT_STATUS_OK; + +out: + set_auth_errors(response, status); + response->data.auth.authoritative = state->authoritative; + response->result = WINBINDD_PENDING; + return NT_STATUS(response->data.auth.nt_status); +} diff --git a/source3/winbindd/winbindd_pam_chauthtok.c b/source3/winbindd/winbindd_pam_chauthtok.c new file mode 100644 index 0000000..e778df8 --- /dev/null +++ b/source3/winbindd/winbindd_pam_chauthtok.c @@ -0,0 +1,204 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_PAM_CHAUTHTOK + Copyright (C) Volker Lendecke 2010 + + 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.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +static void fill_in_password_policy(struct winbindd_response *r, + const struct samr_DomInfo1 *p) +{ + r->data.auth.policy.min_length_password = + p->min_password_length; + r->data.auth.policy.password_history = + p->password_history_length; + r->data.auth.policy.password_properties = + p->password_properties; + r->data.auth.policy.expire = + nt_time_to_unix_abs((const NTTIME *)&(p->max_password_age)); + r->data.auth.policy.min_passwordage = + nt_time_to_unix_abs((const NTTIME *)&(p->min_password_age)); +} + +struct winbindd_pam_chauthtok_state { + struct wbint_PamAuthChangePassword r; +}; + +static void winbindd_pam_chauthtok_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_pam_chauthtok_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_pam_chauthtok_state *state; + struct winbindd_domain *contact_domain; + char *namespace = NULL; + char *domain = NULL; + char *user = NULL; + char *chauthtok_user = NULL; + char *mapped_user; + NTSTATUS status; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_pam_chauthtok_state); + if (req == NULL) { + return NULL; + } + + /* Ensure null termination */ + request->data.chauthtok.user[ + sizeof(request->data.chauthtok.user)-1]='\0'; + + DEBUG(3, ("[%5lu]: pam chauthtok %s\n", (unsigned long)cli->pid, + request->data.chauthtok.user)); + + status = normalize_name_unmap(state, request->data.chauthtok.user, + &mapped_user); + + if (NT_STATUS_IS_OK(status) || + NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) { + fstrcpy(request->data.chauthtok.user, mapped_user); + } + + chauthtok_user = request->data.chauthtok.user; + ok = canonicalize_username(req, + &chauthtok_user, + &namespace, + &domain, + &user); + if (!ok) { + DEBUG(10, ("winbindd_pam_chauthtok: canonicalize_username %s " + "failed with\n", request->data.chauthtok.user)); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return tevent_req_post(req, ev); + } + + fstrcpy(request->data.chauthtok.user, chauthtok_user); + + contact_domain = find_domain_from_name(namespace); + if (contact_domain == NULL) { + DEBUG(3, ("Cannot change password for [%s] -> [%s]\\[%s] " + "as %s is not a trusted domain\n", + request->data.chauthtok.user, domain, user, domain)); + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return tevent_req_post(req, ev); + } + + state->r.in.client_pid = request->pid; + state->r.in.flags = request->flags; + + state->r.in.client_name = talloc_strdup(state, request->client_name); + if (tevent_req_nomem(state->r.in.client_name, req)) { + return tevent_req_post(req, ev); + } + + state->r.in.user = talloc_strdup(state, request->data.chauthtok.user); + if (tevent_req_nomem(state->r.in.user, req)) { + return tevent_req_post(req, ev); + } + + state->r.in.old_password = talloc_strdup(state, + request->data.chauthtok.oldpass); + if (tevent_req_nomem(state->r.in.old_password, req)) { + return tevent_req_post(req, ev); + } + + state->r.in.new_password = talloc_strdup(state, + request->data.chauthtok.newpass); + if (tevent_req_nomem(state->r.in.new_password, req)) { + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_PamAuthChangePassword_r_send(state, + global_event_context(), + dom_child_handle(contact_domain), + &state->r); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_pam_chauthtok_done, req); + return req; +} + +static void winbindd_pam_chauthtok_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_pam_chauthtok_state *state = tevent_req_data( + req, struct winbindd_pam_chauthtok_state); + NTSTATUS status; + + status = dcerpc_wbint_PamAuthChangePassword_r_recv(subreq, state); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS winbindd_pam_chauthtok_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_pam_chauthtok_state *state = tevent_req_data( + req, struct winbindd_pam_chauthtok_state); + NTSTATUS status = NT_STATUS_OK; + + if (tevent_req_is_nterror(req, &status)) { + set_auth_errors(response, status); + return status; + } + + response->result = WINBINDD_PENDING; + + set_auth_errors(response, state->r.out.result); + if (*state->r.out.dominfo != NULL) { + fill_in_password_policy(response, *state->r.out.dominfo); + } + response->data.auth.reject_reason = *state->r.out.reject_reason; + + if (state->r.in.flags & WBFLAG_PAM_CACHED_LOGIN) { + + /* Update the single sign-on memory creds. */ + status = winbindd_replace_memory_creds( + state->r.in.user, state->r.in.new_password); + + DEBUG(10, ("winbindd_replace_memory_creds returned %s\n", + nt_errstr(status))); + + /* + * When we login from gdm or xdm and password expires, + * we change password, but there are no memory + * credentials. So, winbindd_replace_memory_creds() + * returns NT_STATUS_OBJECT_NAME_NOT_FOUND. This is + * not a failure. --- BoYang + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + status = NT_STATUS_OK; + } + } + + return NT_STATUS(response->data.auth.nt_status); +} diff --git a/source3/winbindd/winbindd_pam_chng_pswd_auth_crap.c b/source3/winbindd/winbindd_pam_chng_pswd_auth_crap.c new file mode 100644 index 0000000..8b69f02 --- /dev/null +++ b/source3/winbindd/winbindd_pam_chng_pswd_auth_crap.c @@ -0,0 +1,171 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP + Copyright (C) Volker Lendecke 2010 + + 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.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_pam_chng_pswd_auth_crap_state { + struct wbint_PamAuthCrapChangePassword r; +}; + +static void winbindd_pam_chng_pswd_auth_crap_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_pam_chng_pswd_auth_crap_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_pam_chng_pswd_auth_crap_state *state; + struct winbindd_domain *domain; + const char *domain_name; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_pam_chng_pswd_auth_crap_state); + if (req == NULL) { + return NULL; + } + + /* Ensure null termination */ + request->data.chng_pswd_auth_crap.user[ + sizeof(request->data.chng_pswd_auth_crap.user)-1]='\0'; + request->data.chng_pswd_auth_crap.domain[ + sizeof(request->data.chng_pswd_auth_crap.domain)-1]=0; + + DEBUG(3, ("[%5lu]: pam change pswd auth crap domain: %s user: %s\n", + (unsigned long)cli->pid, + request->data.chng_pswd_auth_crap.domain, + request->data.chng_pswd_auth_crap.user)); + + domain_name = NULL; + if (*request->data.chng_pswd_auth_crap.domain != '\0') { + domain_name = request->data.chng_pswd_auth_crap.domain; + } else if (lp_winbind_use_default_domain()) { + domain_name = lp_workgroup(); + } + + domain = NULL; + if (domain_name != NULL) { + domain = find_domain_from_name(domain_name); + } + + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return tevent_req_post(req, ev); + } + + state->r.in.client_pid = request->pid; + state->r.in.client_name = talloc_strdup(state, request->client_name); + if (tevent_req_nomem(state->r.in.client_name, req)) { + return tevent_req_post(req, ev); + } + + state->r.in.domain = talloc_strdup(state, domain_name); + if (tevent_req_nomem(state->r.in.domain, req)) { + return tevent_req_post(req, ev); + } + state->r.in.user = talloc_strdup(state, + request->data.chng_pswd_auth_crap.user); + if (tevent_req_nomem(state->r.in.user, req)) { + return tevent_req_post(req, ev); + } + + state->r.in.new_nt_pswd = data_blob_talloc(state, + request->data.chng_pswd_auth_crap.new_nt_pswd, + request->data.chng_pswd_auth_crap.new_nt_pswd_len); + if (tevent_req_nomem(state->r.in.new_nt_pswd.data, req)) { + return tevent_req_post(req, ev); + } + + state->r.in.old_nt_hash_enc = data_blob_talloc(state, + request->data.chng_pswd_auth_crap.old_nt_hash_enc, + request->data.chng_pswd_auth_crap.old_nt_hash_enc_len); + if (tevent_req_nomem(state->r.in.old_nt_hash_enc.data, req)) { + return tevent_req_post(req, ev); + } + + if (request->data.chng_pswd_auth_crap.new_lm_pswd_len > 0) { + state->r.in.new_lm_pswd = data_blob_talloc(state, + request->data.chng_pswd_auth_crap.new_lm_pswd, + request->data.chng_pswd_auth_crap.new_lm_pswd_len); + if (tevent_req_nomem(state->r.in.new_lm_pswd.data, req)) { + return tevent_req_post(req, ev); + } + + state->r.in.old_lm_hash_enc = data_blob_talloc(state, + request->data.chng_pswd_auth_crap.old_lm_hash_enc, + request->data.chng_pswd_auth_crap.old_lm_hash_enc_len); + if (tevent_req_nomem(state->r.in.old_lm_hash_enc.data, req)) { + return tevent_req_post(req, ev); + } + } else { + state->r.in.new_lm_pswd = data_blob_null; + state->r.in.old_lm_hash_enc = data_blob_null; + } + + subreq = dcerpc_wbint_PamAuthCrapChangePassword_r_send(state, + global_event_context(), + dom_child_handle(domain), + &state->r); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_pam_chng_pswd_auth_crap_done, + req); + return req; +} + +static void winbindd_pam_chng_pswd_auth_crap_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_pam_chng_pswd_auth_crap_state *state = tevent_req_data( + req, struct winbindd_pam_chng_pswd_auth_crap_state); + NTSTATUS status; + + status = dcerpc_wbint_PamAuthCrapChangePassword_r_recv(subreq, state); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS winbindd_pam_chng_pswd_auth_crap_recv( + struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_pam_chng_pswd_auth_crap_state *state = tevent_req_data( + req, struct winbindd_pam_chng_pswd_auth_crap_state); + NTSTATUS status = NT_STATUS_OK; + + if (tevent_req_is_nterror(req, &status)) { + set_auth_errors(response, status); + return status; + } + + response->result = WINBINDD_PENDING; + set_auth_errors(response, state->r.out.result); + + return NT_STATUS(response->data.auth.nt_status); +} diff --git a/source3/winbindd/winbindd_pam_logoff.c b/source3/winbindd/winbindd_pam_logoff.c new file mode 100644 index 0000000..c799eb5 --- /dev/null +++ b/source3/winbindd/winbindd_pam_logoff.c @@ -0,0 +1,183 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_PAM_LOGOFF + Copyright (C) Volker Lendecke 2010 + + 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 "util/debug.h" +#include "winbindd.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "lib/util/string_wrappers.h" + +struct winbindd_pam_logoff_state { + struct wbint_PamLogOff r; +}; + +static void winbindd_pam_logoff_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_pam_logoff_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_pam_logoff_state *state; + struct winbindd_domain *domain; + char *name_namespace = NULL; + char *name_domain = NULL; + char *user = NULL; + char *logoff_user = NULL; + + uid_t caller_uid; + gid_t caller_gid; + int res; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_pam_logoff_state); + if (req == NULL) { + return NULL; + } + D_NOTICE("[%s (%u)] Winbind external command PAM_LOGOFF start.\n" + "Username '%s' is used during logoff.\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.auth.user); + /* Ensure null termination */ + /* Ensure null termination */ + request->data.logoff.user[sizeof(request->data.logoff.user)-1]='\0'; + request->data.logoff.krb5ccname[ + sizeof(request->data.logoff.krb5ccname)-1]='\0'; + + if (request->data.logoff.uid == (uid_t)-1) { + goto failed; + } + + logoff_user = request->data.logoff.user; + + ok = canonicalize_username(req, + &logoff_user, + &name_namespace, + &name_domain, + &user); + if (!ok) { + goto failed; + } + + fstrcpy(request->data.logoff.user, logoff_user); + + domain = find_auth_domain(request->flags, name_namespace); + if (domain == NULL) { + goto failed; + } + + caller_uid = (uid_t)-1; + + res = getpeereid(cli->sock, &caller_uid, &caller_gid); + if (res != 0) { + D_WARNING("winbindd_pam_logoff: failed to check peerid: %s\n", + strerror(errno)); + goto failed; + } + + switch (caller_uid) { + case -1: + goto failed; + case 0: + /* root must be able to logoff any user - gd */ + break; + default: + if (caller_uid != request->data.logoff.uid) { + D_WARNING("caller requested invalid uid\n"); + goto failed; + } + break; + } + + state->r.in.client_name = talloc_strdup(state, request->client_name); + if (tevent_req_nomem(state->r.in.client_name, req)) { + return tevent_req_post(req, ev); + } + state->r.in.client_pid = request->pid; + + state->r.in.flags = request->flags; + state->r.in.user = talloc_strdup(state, request->data.logoff.user); + if (tevent_req_nomem(state->r.in.user, req)) { + return tevent_req_post(req, ev); + } + state->r.in.uid = request->data.logoff.uid; + state->r.in.krb5ccname = talloc_strdup(state, + request->data.logoff.krb5ccname); + if (tevent_req_nomem(state->r.in.krb5ccname, req)) { + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_PamLogOff_r_send(state, + global_event_context(), + dom_child_handle(domain), + &state->r); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_pam_logoff_done, req); + return req; + +failed: + tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER); + return tevent_req_post(req, ev); +} + +static void winbindd_pam_logoff_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_pam_logoff_state *state = tevent_req_data( + req, struct winbindd_pam_logoff_state); + NTSTATUS status; + + status = dcerpc_wbint_PamLogOff_r_recv(subreq, state); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS winbindd_pam_logoff_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_pam_logoff_state *state = tevent_req_data( + req, struct winbindd_pam_logoff_state); + NTSTATUS status = NT_STATUS_OK; + + D_NOTICE("Winbind external command PAM_LOGOFF end.\n"); + if (tevent_req_is_nterror(req, &status)) { + set_auth_errors(response, status); + return status; + } + + response->result = WINBINDD_PENDING; + set_auth_errors(response, state->r.out.result); + + if (NT_STATUS_IS_OK(state->r.out.result)) { + winbindd_delete_memory_creds(state->r.in.user); + } + + return NT_STATUS(response->data.auth.nt_status); +} diff --git a/source3/winbindd/winbindd_ping_dc.c b/source3/winbindd/winbindd_ping_dc.c new file mode 100644 index 0000000..8f56a9e --- /dev/null +++ b/source3/winbindd/winbindd_ping_dc.c @@ -0,0 +1,140 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_PING_DC + Copyright (C) Volker Lendecke 2009 + + 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.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +struct winbindd_ping_dc_state { + const char *dcname; + NTSTATUS result; +}; + +static void winbindd_ping_dc_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_ping_dc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_ping_dc_state *state; + struct winbindd_domain *domain; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_ping_dc_state); + if (req == NULL) { + return NULL; + } + + if (request->domain_name[0] == '\0') { + /* preserve old behavior, when no domain name is given */ + domain = find_our_domain(); + } else { + domain = find_trust_from_name_noinit(request->domain_name); + } + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + if (domain->internal) { + const char *d = lp_dnsdomain(); + const char *n = lp_netbios_name(); + + /* + * Internal domains are passdb based, we can always + * contact them. + */ + + if (d != NULL) { + char *h; + h = strlower_talloc(mem_ctx, n); + if (tevent_req_nomem(h, req)) { + return tevent_req_post(req, ev); + } + + state->dcname = talloc_asprintf(state, "%s.%s", h, d); + TALLOC_FREE(h); + + if (tevent_req_nomem(state->dcname, req)) { + return tevent_req_post(req, ev); + } + } else { + state->dcname = talloc_strdup(state, n); + if (tevent_req_nomem(state->dcname, req)) { + return tevent_req_post(req, ev); + } + } + + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = dcerpc_wbint_PingDc_send(state, ev, dom_child_handle(domain), + &state->dcname); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_ping_dc_done, req); + return req; +} + +static void winbindd_ping_dc_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_ping_dc_state *state = tevent_req_data( + req, struct winbindd_ping_dc_state); + NTSTATUS status, result; + + status = dcerpc_wbint_PingDc_recv(subreq, state, &result); + state->result = result; + if (any_nt_status_not_ok(status, result, &status)) { + tevent_req_nterror(req, status); + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_ping_dc_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + struct winbindd_ping_dc_state *state = tevent_req_data( + req, struct winbindd_ping_dc_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + + if (!NT_STATUS_IS_OK(state->result)) { + set_auth_errors(presp, state->result); + } + + if (state->dcname) { + presp->extra_data.data = talloc_strdup(presp, state->dcname); + if (presp->extra_data.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + presp->length += strlen((char *)presp->extra_data.data) + 1; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_proto.h b/source3/winbindd/winbindd_proto.h new file mode 100644 index 0000000..a5e8c8b --- /dev/null +++ b/source3/winbindd/winbindd_proto.h @@ -0,0 +1,1059 @@ +/* + * Unix SMB/CIFS implementation. + * collected prototypes header + * + * frozen from "make proto" in May 2008 + * + * Copyright (C) Michael Adam 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/>. + */ + +#ifndef _WINBINDD_PROTO_H_ +#define _WINBINDD_PROTO_H_ + +/* The following definitions come from winbindd/winbindd.c */ +struct imessaging_context *winbind_imessaging_context(void); +void winbindd_terminate(bool is_parent); +bool winbindd_setup_sig_term_handler(bool parent); +bool winbindd_setup_stdin_handler(bool parent, bool foreground); +bool winbindd_setup_sig_hup_handler(const char *lfile); +bool winbindd_use_idmap_cache(void); +bool winbindd_use_cache(void); +void winbindd_set_use_cache(bool use_cache); +char *get_winbind_priv_pipe_dir(void); +void winbindd_flush_caches(void); +void winbind_debug_call_depth_setup(size_t *depth); +void winbind_call_flow(void *private_data, + enum tevent_thread_call_depth_cmd cmd, + struct tevent_req *req, + size_t depth, + const char *fname); +bool winbindd_reload_services_file(const char *lfile); + +/* The following definitions come from winbindd/winbindd_ads.c */ + +/* The following definitions come from winbindd/winbindd_rpc.c */ + +NTSTATUS winbindd_lookup_sids(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + uint32_t num_sids, + const struct dom_sid *sids, + char ***domains, + char ***names, + enum lsa_SidType **types); +NTSTATUS rpc_lookup_sids(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + struct lsa_SidArray *sids, + struct lsa_RefDomainList **pdomains, + struct lsa_TransNameArray **pnames); + +/* The following definitions come from winbindd/winbindd_cache.c */ + +NTSTATUS wb_cache_query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **prids); +NTSTATUS wb_cache_enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info); +NTSTATUS wb_cache_enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info); +NTSTATUS wb_cache_name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + uint32_t flags, + struct dom_sid *sid, + enum lsa_SidType *type); +NTSTATUS wb_cache_sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + char **domain_name, + char **name, + enum lsa_SidType *type); +NTSTATUS wb_cache_rids_to_names(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *domain_sid, + uint32_t *rids, + size_t num_rids, + char **domain_name, + char ***names, + enum lsa_SidType **types); +NTSTATUS wb_cache_lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *pnum_sids, + struct dom_sid **psids); +NTSTATUS wb_cache_lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *num_aliases, + uint32_t **alias_rids); +NTSTATUS wb_cache_lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem, + char ***names, + uint32_t **name_types); +NTSTATUS wb_cache_lookup_aliasmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem); +NTSTATUS wb_cache_sequence_number(struct winbindd_domain *domain, + uint32_t *seq); +NTSTATUS wb_cache_lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *policy); +NTSTATUS wb_cache_password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *policy); +NTSTATUS wb_cache_trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *trusts); + +NTSTATUS wcache_cached_creds_exist(struct winbindd_domain *domain, const struct dom_sid *sid); +NTSTATUS wcache_get_creds(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + const uint8_t **cached_nt_pass, + const uint8_t **cached_salt); +NTSTATUS wcache_save_creds(struct winbindd_domain *domain, + const struct dom_sid *sid, + const uint8_t nt_pass[NT_HASH_LEN]); +void wcache_invalidate_samlogon(struct winbindd_domain *domain, + const struct dom_sid *user_sid); +bool wcache_invalidate_cache(void); +bool wcache_invalidate_cache_noinit(void); +bool initialize_winbindd_cache(void); +void close_winbindd_cache(void); +bool lookup_cached_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid, + char **domain_name, char **name, + enum lsa_SidType *type); +bool lookup_cached_name(const char *namespace, + const char *domain_name, + const char *name, + struct dom_sid *sid, + enum lsa_SidType *type); +void cache_name2sid_trusted(struct winbindd_domain *domain, + const char *domain_name, + const char *name, + enum lsa_SidType type, + const struct dom_sid *sid); +void cache_name2sid(struct winbindd_domain *domain, + const char *domain_name, const char *name, + enum lsa_SidType type, const struct dom_sid *sid); +NTSTATUS wcache_query_user_fullname(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + const char **full_name); + +NTSTATUS wcache_count_cached_creds(struct winbindd_domain *domain, int *count); +NTSTATUS wcache_remove_oldest_cached_creds(struct winbindd_domain *domain, const struct dom_sid *sid) ; +bool set_global_winbindd_state_offline(void); +void set_global_winbindd_state_online(void); +bool get_global_winbindd_state_offline(void); +int winbindd_validate_cache(void); +int winbindd_validate_cache_nobackup(void); +bool winbindd_cache_validate_and_initialize(void); +bool wcache_tdc_fetch_list( struct winbindd_tdc_domain **domains, size_t *num_domains ); +bool wcache_tdc_add_domain( struct winbindd_domain *domain ); +struct winbindd_tdc_domain * wcache_tdc_fetch_domain( TALLOC_CTX *ctx, const char *name ); +void wcache_tdc_clear( void ); +bool wcache_store_seqnum(const char *domain_name, uint32_t seqnum, + time_t last_seq_check); +bool wcache_fetch_ndr(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain, + uint32_t opnum, const DATA_BLOB *req, DATA_BLOB *resp); +void wcache_store_ndr(struct winbindd_domain *domain, uint32_t opnum, + const DATA_BLOB *req, const DATA_BLOB *resp); + +/* The following definitions come from winbindd/winbindd_ccache_access.c */ + +bool winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state); +enum winbindd_result winbindd_dual_ccache_ntlm_auth(struct winbindd_domain *domain, + struct winbindd_cli_state *state); +bool winbindd_ccache_save(struct winbindd_cli_state *state); + +/* The following definitions come from winbindd/winbindd_cm.c */ +void winbind_msg_domain_offline(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_domain_online(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); + +void set_domain_offline(struct winbindd_domain *domain); +void set_domain_online_request(struct winbindd_domain *domain); + +struct ndr_interface_table; +NTSTATUS wb_open_internal_pipe(TALLOC_CTX *mem_ctx, + const struct ndr_interface_table *table, + struct rpc_pipe_client **ret_pipe); +void invalidate_cm_connection(struct winbindd_domain *domain); +void close_conns_after_fork(void); +NTSTATUS init_dc_connection(struct winbindd_domain *domain, bool need_rw_dc); +NTSTATUS cm_connect_sam(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, + bool need_rw_dc, + struct rpc_pipe_client **cli, struct policy_handle *sam_handle); +NTSTATUS cm_connect_lsa(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli, struct policy_handle *lsa_policy); +NTSTATUS cm_connect_lsat(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **cli, + struct policy_handle *lsa_policy); +NTSTATUS cm_connect_netlogon(struct winbindd_domain *domain, + struct rpc_pipe_client **cli); +NTSTATUS cm_connect_netlogon_secure(struct winbindd_domain *domain, + struct rpc_pipe_client **cli, + struct netlogon_creds_cli_context **ppdc); +bool fetch_current_dc_from_gencache(TALLOC_CTX *mem_ctx, + const char *domain_name, + char **p_dc_name, char **p_dc_ip); + +/* The following definitions come from winbindd/winbindd_cred_cache.c */ + +bool ccache_entry_exists(const char *username); +bool ccache_entry_identical(const char *username, + uid_t uid, + const char *ccname); +void ccache_remove_all_after_fork(void); +void ccache_regain_all_now(void); +NTSTATUS add_ccache_to_list(const char *princ_name, + const char *ccname, + const char *username, + const char *password, + const char *realm, + uid_t uid, + time_t create_time, + time_t ticket_end, + time_t renew_until, + bool postponed_request, + const char *canon_principal, + const char *canon_realm); +NTSTATUS remove_ccache(const char *username); +struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username); +NTSTATUS winbindd_add_memory_creds(const char *username, + uid_t uid, + const char *pass); +NTSTATUS winbindd_delete_memory_creds(const char *username); +NTSTATUS winbindd_replace_memory_creds(const char *username, + const char *pass); + +/* The following definitions come from winbindd/winbindd_creds.c */ + +NTSTATUS winbindd_get_creds(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + struct netr_SamInfo3 **info3, + const uint8_t **cached_nt_pass, + const uint8_t **cred_salt); +NTSTATUS winbindd_store_creds(struct winbindd_domain *domain, + const char *user, + const char *pass, + struct netr_SamInfo3 *info3); +NTSTATUS winbindd_update_creds_by_info3(struct winbindd_domain *domain, + const char *user, + const char *pass, + struct netr_SamInfo3 *info3); +NTSTATUS winbindd_update_creds_by_name(struct winbindd_domain *domain, + const char *user, + const char *pass); + +/* The following definitions come from winbindd/winbindd_domain.c */ + +void setup_domain_child(struct winbindd_domain *domain); + +/* The following definitions come from winbindd/winbindd_dual.c */ + +struct dcerpc_binding_handle *dom_child_handle(struct winbindd_domain *domain); + +struct tevent_req *wb_child_request_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_child *child, + struct winbindd_request *request); +int wb_child_request_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct winbindd_response **presponse, int *err); +struct tevent_req *wb_domain_request_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain, + struct winbindd_request *request); +int wb_domain_request_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct winbindd_response **presponse, int *err); + +void setup_child(struct winbindd_domain *domain, struct winbindd_child *child, + const char *logprefix, + const char *logname); +void winbind_child_died(pid_t pid); +void winbindd_flush_negative_conn_cache(struct winbindd_domain *domain); +void winbind_msg_debug(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_disconnect_dc_parent(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_offline(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_online(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_onlinestatus(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_dump_event_list(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_dump_domain_list(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_ip_dropped(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_disconnect_dc(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbind_msg_ip_dropped_parent(struct messaging_context *msg_ctx, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +void winbindd_msg_reload_services_parent(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); +NTSTATUS winbindd_reinit_after_fork(const struct winbindd_child *myself, + const char *logfilename); +struct winbindd_domain *wb_child_domain(void); +bool add_trusted_domains_dc(void); + +/* The following definitions come from winbindd/winbindd_group.c */ +bool fill_grent(TALLOC_CTX *mem_ctx, struct winbindd_gr *gr, + const char *dom_name, const char *gr_name, gid_t unix_gid); + +struct db_context; +NTSTATUS winbindd_print_groupmembers(struct db_context *members, + TALLOC_CTX *mem_ctx, + int *num_members, char **result); + + +/* The following definitions come from winbindd/winbindd_idmap.c */ + +struct tevent_req *wb_parent_idmap_setup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev); +NTSTATUS wb_parent_idmap_setup_recv(struct tevent_req *req, + const struct wb_parent_idmap_config **_cfg); + +NTSTATUS init_idmap_child(TALLOC_CTX *mem_ctx); +struct winbindd_child *idmap_child(void); +bool is_idmap_child(const struct winbindd_child *child); +pid_t idmap_child_pid(void); +struct dcerpc_binding_handle *idmap_child_handle(void); +struct idmap_domain *idmap_find_domain_with_sid(const char *domname, + const struct dom_sid *sid); +const char *idmap_config_const_string(const char *domname, const char *option, + const char *def); +bool idmap_config_bool(const char *domname, const char *option, bool def); +int idmap_config_int(const char *domname, const char *option, int def); +const char **idmap_config_string_list(const char *domname, + const char *option, + const char **def); +bool domain_has_idmap_config(const char *domname); +bool lp_scan_idmap_domains(bool (*fn)(const char *domname, + void *private_data), + void *private_data); + +/* The following definitions come from winbindd/winbindd_locator.c */ + +NTSTATUS init_locator_child(TALLOC_CTX *mem_ctx); +struct winbindd_child *locator_child(void); +struct dcerpc_binding_handle *locator_child_handle(void); + +/* The following definitions come from winbindd/winbindd_misc.c */ + +bool winbindd_list_trusted_domains(struct winbindd_cli_state *state); +bool winbindd_dc_info(struct winbindd_cli_state *state); +bool winbindd_ping(struct winbindd_cli_state *state); +bool winbindd_info(struct winbindd_cli_state *state); +bool winbindd_interface_version(struct winbindd_cli_state *state); +bool winbindd_domain_name(struct winbindd_cli_state *state); +bool winbindd_netbios_name(struct winbindd_cli_state *state); +bool winbindd_priv_pipe_dir(struct winbindd_cli_state *state); + +/* The following definitions come from winbindd/winbindd_ndr.c */ +struct ndr_print; +void ndr_print_winbindd_child(struct ndr_print *ndr, + const char *name, + const struct winbindd_child *r); +void ndr_print_winbindd_cm_conn(struct ndr_print *ndr, + const char *name, + const struct winbindd_cm_conn *r); +void ndr_print_winbindd_methods(struct ndr_print *ndr, + const char *name, + const struct winbindd_methods *r); +void ndr_print_winbindd_domain(struct ndr_print *ndr, + const char *name, + const struct winbindd_domain *r); + +/* The following definitions come from winbindd/winbindd_pam.c */ + +bool check_request_flags(uint32_t flags); +NTSTATUS append_auth_data(TALLOC_CTX *mem_ctx, + struct winbindd_response *resp, + uint32_t request_flags, + uint16_t validation_level, + union netr_Validation *validation, + const char *name_domain, + const char *name_user); +NTSTATUS extra_data_to_sid_array(const char *group_sid, + TALLOC_CTX *mem_ctx, + struct wbint_SidArray **_sid_array); +uid_t get_uid_from_request(struct winbindd_request *request); +struct winbindd_domain *find_auth_domain(uint8_t flags, + const char *domain_name); +struct pipes_struct; +struct wbint_PamAuth; +NTSTATUS _wbint_PamAuth(struct pipes_struct *p, + struct wbint_PamAuth *r); +NTSTATUS _wbint_PamAuthCrap(struct pipes_struct *p, + struct wbint_PamAuthCrap *r); +NTSTATUS _wbint_PamAuthChangePassword(struct pipes_struct *p, + struct wbint_PamAuthChangePassword *r); +NTSTATUS _wbint_PamLogOff(struct pipes_struct *p, + struct wbint_PamLogOff *r); +NTSTATUS _wbint_PamAuthCrapChangePassword(struct pipes_struct *p, + struct wbint_PamAuthCrapChangePassword *r); +NTSTATUS winbindd_pam_auth_pac_verify(struct winbindd_cli_state *state, + TALLOC_CTX *mem_ctx, + bool *p_is_trusted, + uint16_t *p_validation_level, + union netr_Validation **p_validation); + +NTSTATUS winbind_dual_SamLogon(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + bool interactive, + uint32_t logon_parameters, + const char *name_user, + const char *name_domain, + const char *workstation, + const uint64_t logon_id, + const char *client_name, + const int pid, + DATA_BLOB chal, + DATA_BLOB lm_response, + DATA_BLOB nt_response, + const struct tsocket_address *remote, + const struct tsocket_address *local, + uint8_t *authoritative, + bool skip_sam, + uint32_t *flags, + uint16_t *_validation_level, + union netr_Validation **_validation); + +/* The following definitions come from winbindd/winbindd_util.c */ + +struct winbindd_domain *domain_list(void); +struct winbindd_domain *wb_next_domain(struct winbindd_domain *domain); +bool set_routing_domain(struct winbindd_domain *domain, + struct winbindd_domain *routing_domain); +bool add_trusted_domain_from_auth(uint16_t validation_level, + struct info3_text *info3, + struct info6_text *info6); +bool domain_is_forest_root(const struct winbindd_domain *domain); +void rescan_trusted_domains(struct tevent_context *ev, struct tevent_timer *te, + struct timeval now, void *private_data); +void winbindd_ping_offline_domains(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval now, + void *private_data); +bool init_domain_list(void); +struct winbindd_domain *find_domain_from_name_noinit(const char *domain_name); +struct winbindd_domain *find_trust_from_name_noinit(const char *domain_name); +struct winbindd_domain *find_domain_from_name(const char *domain_name); +struct winbindd_domain *find_domain_from_sid_noinit(const struct dom_sid *sid); +struct winbindd_domain *find_trust_from_sid_noinit(const struct dom_sid *sid); +struct winbindd_domain *find_domain_from_sid(const struct dom_sid *sid); +struct winbindd_domain *find_our_domain(void); +struct winbindd_domain *find_default_route_domain(void); +struct winbindd_domain *find_lookup_domain_from_sid(const struct dom_sid *sid); +struct winbindd_domain *find_lookup_domain_from_name(const char *domain_name); +/** + * Parse a DOMAIN\user or UPN string into a domain, namespace and a user + * + * @param[in] ctx talloc context + * @param[in] domuser a DOMAIN\user or UPN string + * @param[out] namespace + * @param[out] domain + * @param[out] user + * @return bool indicating success or failure + */ +bool parse_domain_user(TALLOC_CTX *ctx, + const char *domuser, + char **namespace, + char **domain, + char **user); +/** + * Ensure an incoming username from NSS is fully qualified. Replace the + * incoming username with DOMAIN <separator> user. Additionally returns + * the same values as parse_domain_user() as out params. + * Used to ensure all names are fully qualified within winbindd. + * Used by the NSS protocols of auth, chauthtok, logoff and ccache_ntlm_auth. + * The protocol definitions of auth_crap, chng_pswd_auth_crap + * really should be changed to use this instead of doing things + * by hand. JRA. + * + * @param[in] mem_ctx talloc context + * @param[in,out] username_inout populated with fully qualified name + with format 'DOMAIN <separator> user' where DOMAIN and + user are determined by the output of parse_domain_user() + * @param[out] namespace populated with namespace returned from + parse_domain_user() + * @param[out] domain populated with domain returned from + parse_domain_user() + * @param[out] populated with user returned from + parse_domain_user() + * @return bool indicating success or failure + */ +bool canonicalize_username(TALLOC_CTX *mem_ctx, + char **username_inout, + char **namespace, + char **domain, + char **user); +char *fill_domain_username_talloc(TALLOC_CTX *ctx, + const char *domain, + const char *user, + bool can_assume); +struct winbindd_cli_state *winbindd_client_list(void); +struct winbindd_cli_state *winbindd_client_list_tail(void); +struct winbindd_cli_state * +winbindd_client_list_prev(struct winbindd_cli_state *cli); +void winbindd_add_client(struct winbindd_cli_state *cli); +void winbindd_remove_client(struct winbindd_cli_state *cli); +void winbindd_promote_client(struct winbindd_cli_state *cli); +int winbindd_num_clients(void); +NTSTATUS lookup_usergroups_cached(TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *p_num_groups, struct dom_sid **user_sids); + +NTSTATUS normalize_name_map(TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + char **normalized); +NTSTATUS normalize_name_unmap(TALLOC_CTX *mem_ctx, + const 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); +void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain); +void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain); +void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain); +void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain); +void set_auth_errors(struct winbindd_response *resp, NTSTATUS result); +bool is_domain_offline(const struct winbindd_domain *domain); +bool is_domain_online(const struct winbindd_domain *domain); +bool parse_sidlist(TALLOC_CTX *mem_ctx, const char *sidstr, + struct dom_sid **sids, uint32_t *num_sids); +bool parse_xidlist(TALLOC_CTX *mem_ctx, const char *xidstr, + struct unixid **pxids, uint32_t *pnum_xids); + +/* The following definitions come from winbindd/winbindd_wins.c */ + +void winbindd_wins_byname(struct winbindd_cli_state *state); + +struct dcerpc_binding_handle *wbint_binding_handle(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + struct winbindd_child *child); +enum winbindd_result winbindd_dual_ndrcmd(struct winbindd_domain *domain, + struct winbindd_cli_state *state); + +struct tevent_req *wb_lookupsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid); +NTSTATUS wb_lookupsid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + enum lsa_SidType *type, const char **domain, + const char **name); + +struct tevent_req *winbindd_lookupsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_lookupsid_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_lookupsids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_lookupsids_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *wb_lookupname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *namespace, + const char *dom_name, + const char *name, + uint32_t flags); +NTSTATUS wb_lookupname_recv(struct tevent_req *req, struct dom_sid *sid, + enum lsa_SidType *type); + +struct tevent_req *winbindd_lookupname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_lookupname_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_allocate_uid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_allocate_uid_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_allocate_gid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_allocate_gid_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *wb_queryuser_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *user_sid); +NTSTATUS wb_queryuser_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct wbint_userinfo **pinfo); + +struct tevent_req *wb_getpwsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *user_sid, + struct winbindd_pw *pw); +NTSTATUS wb_getpwsid_recv(struct tevent_req *req); + +struct tevent_req *winbindd_getpwsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getpwsid_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_getpwnam_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getpwnam_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_getpwuid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getpwuid_recv(struct tevent_req *req, + struct winbindd_response *response); +struct tevent_req *wb_lookupuseraliases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain, + uint32_t num_sids, + const struct dom_sid *sids); +NTSTATUS wb_lookupuseraliases_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint32_t *num_aliases, uint32_t **aliases); +struct tevent_req *winbindd_getsidaliases_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getsidaliases_recv(struct tevent_req *req, + struct winbindd_response *response); +struct tevent_req *wb_lookupusergroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid); +NTSTATUS wb_lookupusergroups_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint32_t *num_sids, struct dom_sid **sids); + +struct tevent_req *winbindd_getuserdomgroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getuserdomgroups_recv(struct tevent_req *req, + struct winbindd_response *response); +struct tevent_req *wb_gettoken_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid, + bool expand_local_aliases); +NTSTATUS wb_gettoken_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint32_t *num_sids, struct dom_sid **sids); +struct tevent_req *winbindd_getgroups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getgroups_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *wb_seqnum_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain); +NTSTATUS wb_seqnum_recv(struct tevent_req *req, uint32_t *seqnum); + +struct tevent_req *wb_seqnums_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev); +NTSTATUS wb_seqnums_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + int *num_domains, struct winbindd_domain ***domains, + NTSTATUS **statuses, uint32_t **seqnums); + +struct tevent_req *winbindd_show_sequence_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_show_sequence_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *wb_group_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid, + uint32_t num_sids, + enum lsa_SidType *type, + int max_depth); +NTSTATUS wb_group_members_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct db_context **members); + +struct tevent_req *wb_alias_members_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sid, + enum lsa_SidType type, + int max_nesting); +NTSTATUS wb_alias_members_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint32_t *num_sids, + struct dom_sid **sids); + +NTSTATUS add_member_to_db(struct db_context *db, struct dom_sid *sid, + const char *name); + +struct tevent_req *wb_getgrsid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *group_sid, + int max_nesting); +NTSTATUS wb_getgrsid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + const char **domname, const char **name, gid_t *gid, + struct db_context **members); + +struct tevent_req *winbindd_getgrgid_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getgrgid_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_getgrnam_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getgrnam_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_getusersids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getusersids_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_lookuprids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_lookuprids_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *wb_query_user_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain); +NTSTATUS wb_query_user_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + char **users); + +struct tevent_req *wb_query_group_list_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_domain *domain); +NTSTATUS wb_query_group_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + uint32_t *num_users, + struct wbint_Principal **groups); + +struct tevent_req *wb_next_pwent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct getpwent_state *gstate, + struct winbindd_pw *pw); +NTSTATUS wb_next_pwent_recv(struct tevent_req *req); + +struct tevent_req *winbindd_setpwent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_setpwent_recv(struct tevent_req *req, + struct winbindd_response *presp); + +struct tevent_req *winbindd_getpwent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getpwent_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_endpwent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_endpwent_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_dsgetdcname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_dsgetdcname_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *wb_dsgetdcname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *domain_name, + const struct GUID *domain_guid, + const char *site_name, + uint32_t flags); +NTSTATUS wb_dsgetdcname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct netr_DsRGetDCNameInfo **pdcinfo); +NTSTATUS wb_dsgetdcname_gencache_set(const char *domname, + struct netr_DsRGetDCNameInfo *dcinfo); +NTSTATUS wb_dsgetdcname_gencache_get(TALLOC_CTX *mem_ctx, + const char *domname, + struct netr_DsRGetDCNameInfo **dcinfo); + +struct tevent_req *winbindd_getdcname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getdcname_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *wb_next_grent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int max_nesting, + struct getgrent_state *gstate, + struct winbindd_gr *gr); +NTSTATUS wb_next_grent_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct db_context **members); + +struct tevent_req *winbindd_setgrent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_setgrent_recv(struct tevent_req *req, + struct winbindd_response *response); +struct tevent_req *winbindd_getgrent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_getgrent_recv(struct tevent_req *req, + struct winbindd_response *response); +struct tevent_req *winbindd_endgrent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_endgrent_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_list_users_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_list_users_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_list_groups_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_list_groups_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_check_machine_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_check_machine_acct_recv(struct tevent_req *req, + struct winbindd_response *presp); + +struct tevent_req *winbindd_ping_dc_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_ping_dc_recv(struct tevent_req *req, + struct winbindd_response *presp); + +struct tevent_req *winbindd_change_machine_acct_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_change_machine_acct_recv(struct tevent_req *req, + struct winbindd_response *presp); + +struct tevent_req *winbindd_pam_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_pam_auth_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_pam_auth_crap_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_pam_auth_crap_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_pam_chauthtok_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_pam_chauthtok_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_pam_logoff_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_pam_logoff_recv(struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *winbindd_pam_chng_pswd_auth_crap_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_pam_chng_pswd_auth_crap_recv( + struct tevent_req *req, + struct winbindd_response *response); + +struct tevent_req *wb_lookupsids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dom_sid *sids, + uint32_t num_sids); +NTSTATUS wb_lookupsids_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct lsa_RefDomainList **domains, + struct lsa_TransNameArray **names); + +struct tevent_req *wb_sids2xids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct dom_sid *sids, + const uint32_t num_sids); +NTSTATUS wb_sids2xids_recv(struct tevent_req *req, + struct unixid xids[], uint32_t num_xids); +struct tevent_req *winbindd_sids_to_xids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_sids_to_xids_recv(struct tevent_req *req, + struct winbindd_response *response); +struct tevent_req *wb_xids2sids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const struct unixid *xids, + uint32_t num_xids); +NTSTATUS wb_xids2sids_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct dom_sid **sids); +struct tevent_req *winbindd_xids_to_sids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_xids_to_sids_recv(struct tevent_req *req, + struct winbindd_response *response); +struct tevent_req *winbindd_wins_byip_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_wins_byip_recv(struct tevent_req *req, + struct winbindd_response *presp); +struct tevent_req *winbindd_wins_byname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_wins_byname_recv(struct tevent_req *req, + struct winbindd_response *presp); +struct tevent_req *winbindd_domain_info_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request); +NTSTATUS winbindd_domain_info_recv(struct tevent_req *req, + struct winbindd_response *response); + +/* The following definitions come from winbindd/winbindd_samr.c */ + +NTSTATUS open_internal_samr_conn(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + struct rpc_pipe_client **samr_pipe, + struct policy_handle *samr_domain_hnd); +NTSTATUS open_internal_lsa_conn(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **lsa_pipe, + struct policy_handle *lsa_hnd); + +/* The following definitions come from winbindd/winbindd_irpc.c */ +NTSTATUS wb_irpc_register(void); + +/* The following definitions come from winbindd/winbindd_reconnect.c */ +bool reconnect_need_retry(NTSTATUS status, struct winbindd_domain *domain); + +/* The following definitions come from winbindd/winbindd_gpupdate.c */ +void gpupdate_init(void); +void gpupdate_user_init(const char *user); + +/* The following comes from winbindd/winbindd_dual_srv.c */ +bool reset_cm_connection_on_error(struct winbindd_domain *domain, + struct dcerpc_binding_handle *b, + NTSTATUS status); + +#endif /* _WINBINDD_PROTO_H_ */ diff --git a/source3/winbindd/winbindd_reconnect.c b/source3/winbindd/winbindd_reconnect.c new file mode 100644 index 0000000..c49831b --- /dev/null +++ b/source3/winbindd/winbindd_reconnect.c @@ -0,0 +1,354 @@ +/* + Unix SMB/CIFS implementation. + + Wrapper around winbindd_rpc.c to centralize retry logic. + + Copyright (C) Volker Lendecke 2005 + + 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.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +extern struct winbindd_methods msrpc_methods; + +bool reconnect_need_retry(NTSTATUS status, struct winbindd_domain *domain) +{ + if (NT_STATUS_IS_OK(status)) { + return false; + } + + if (!NT_STATUS_IS_ERR(status)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_GROUP)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_ALIAS)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_MEMBER)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_PRIVILEGE)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) { + return false; + } + + reset_cm_connection_on_error(domain, NULL, status); + + return true; +} + +/* List all users */ +static NTSTATUS query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **rids) +{ + NTSTATUS result; + + result = msrpc_methods.query_user_list(domain, mem_ctx, rids); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.query_user_list(domain, mem_ctx, rids); + + return result; +} + +/* list all domain groups */ +static NTSTATUS enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + NTSTATUS result; + + result = msrpc_methods.enum_dom_groups(domain, mem_ctx, + num_entries, info); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.enum_dom_groups(domain, mem_ctx, + num_entries, info); + return result; +} + +/* List all domain groups */ + +static NTSTATUS enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + NTSTATUS result; + + result = msrpc_methods.enum_local_groups(domain, mem_ctx, + num_entries, info); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.enum_local_groups(domain, mem_ctx, + num_entries, info); + + return result; +} + +/* convert a single name to a sid in a domain */ +static NTSTATUS name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + uint32_t flags, + const char **pdom_name, + struct dom_sid *sid, + enum lsa_SidType *type) +{ + NTSTATUS result; + + result = msrpc_methods.name_to_sid(domain, mem_ctx, domain_name, name, + flags, pdom_name, sid, type); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.name_to_sid(domain, mem_ctx, + domain_name, name, flags, + pdom_name, sid, type); + + return result; +} + +/* + convert a domain SID to a user or group name +*/ +static NTSTATUS sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + char **domain_name, + char **name, + enum lsa_SidType *type) +{ + NTSTATUS result; + + result = msrpc_methods.sid_to_name(domain, mem_ctx, sid, + domain_name, name, type); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.sid_to_name(domain, mem_ctx, sid, + domain_name, name, type); + + return result; +} + +static NTSTATUS rids_to_names(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + uint32_t *rids, + size_t num_rids, + char **domain_name, + char ***names, + enum lsa_SidType **types) +{ + NTSTATUS result; + + result = msrpc_methods.rids_to_names(domain, mem_ctx, sid, + rids, num_rids, + domain_name, names, types); + if (reconnect_need_retry(result, domain)) { + result = msrpc_methods.rids_to_names(domain, mem_ctx, sid, + rids, num_rids, + domain_name, names, + types); + } + + return result; +} + +/* Lookup groups a user is a member of. I wish Unix had a call like this! */ +static NTSTATUS lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *num_groups, struct dom_sid **user_gids) +{ + NTSTATUS result; + + result = msrpc_methods.lookup_usergroups(domain, mem_ctx, + user_sid, num_groups, + user_gids); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.lookup_usergroups(domain, mem_ctx, + user_sid, num_groups, + user_gids); + + return result; +} + +static NTSTATUS lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, const struct dom_sid *sids, + uint32_t *num_aliases, uint32_t **alias_rids) +{ + NTSTATUS result; + + result = msrpc_methods.lookup_useraliases(domain, mem_ctx, + num_sids, sids, + num_aliases, + alias_rids); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.lookup_useraliases(domain, mem_ctx, + num_sids, sids, + num_aliases, + alias_rids); + + return result; +} + +/* Lookup alias membership given */ +static NTSTATUS lookup_aliasmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + enum lsa_SidType type, + uint32_t *num_sids, + struct dom_sid **sids) +{ + NTSTATUS result; + + result = msrpc_methods.lookup_aliasmem(domain, + mem_ctx, + sid, + type, + num_sids, + sids); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.lookup_aliasmem(domain, + mem_ctx, + sid, + type, + num_sids, + sids); + + return result; +} + +/* Lookup group membership given a rid. */ +static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem, char ***names, + uint32_t **name_types) +{ + NTSTATUS result; + + result = msrpc_methods.lookup_groupmem(domain, mem_ctx, + group_sid, type, num_names, + sid_mem, names, + name_types); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.lookup_groupmem(domain, mem_ctx, + group_sid, type, + num_names, + sid_mem, names, + name_types); + + return result; +} + +/* find the lockout policy of a domain */ +static NTSTATUS lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *policy) +{ + NTSTATUS result; + + result = msrpc_methods.lockout_policy(domain, mem_ctx, policy); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.lockout_policy(domain, mem_ctx, policy); + + return result; +} + +/* find the password policy of a domain */ +static NTSTATUS password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *policy) +{ + NTSTATUS result; + + result = msrpc_methods.password_policy(domain, mem_ctx, policy); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.password_policy(domain, mem_ctx, policy); + + return result; +} + +/* get a list of trusted domains */ +static NTSTATUS trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *trusts) +{ + NTSTATUS result; + + result = msrpc_methods.trusted_domains(domain, mem_ctx, trusts); + + if (reconnect_need_retry(result, domain)) + result = msrpc_methods.trusted_domains(domain, mem_ctx, + trusts); + + return result; +} + +/* the rpc backend methods are exposed via this structure */ +struct winbindd_methods reconnect_methods = { + False, + query_user_list, + enum_dom_groups, + enum_local_groups, + name_to_sid, + sid_to_name, + rids_to_names, + lookup_usergroups, + lookup_useraliases, + lookup_groupmem, + lookup_aliasmem, + lockout_policy, + password_policy, + trusted_domains, +}; diff --git a/source3/winbindd/winbindd_reconnect_ads.c b/source3/winbindd/winbindd_reconnect_ads.c new file mode 100644 index 0000000..367f4c6 --- /dev/null +++ b/source3/winbindd/winbindd_reconnect_ads.c @@ -0,0 +1,362 @@ +/* + Unix SMB/CIFS implementation. + + Wrapper around winbindd_ads.c to centralize retry logic. + Copyright (C) Christof Schmitt 2016 + + Based on winbindd_reconnect.c + Copyright (C) Volker Lendecke 2005 + + 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.h" + +#ifdef HAVE_ADS + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +extern struct winbindd_methods ads_methods; + +static bool ldap_reconnect_need_retry(NTSTATUS status, + struct winbindd_domain *domain) +{ + if (NT_STATUS_IS_OK(status)) { + return false; + } + + if (!NT_STATUS_IS_ERR(status)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_GROUP)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_ALIAS)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_MEMBER)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_PRIVILEGE)) { + return false; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) { + return false; + } + + return true; +} + +/* List all users */ +static NTSTATUS query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **rids) +{ + NTSTATUS result; + + result = ads_methods.query_user_list(domain, mem_ctx, rids); + + if (ldap_reconnect_need_retry(result, domain)) { + result = ads_methods.query_user_list(domain, mem_ctx, rids); + } + + return result; +} + +/* list all domain groups */ +static NTSTATUS enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + NTSTATUS result; + + result = ads_methods.enum_dom_groups(domain, mem_ctx, + num_entries, info); + + if (ldap_reconnect_need_retry(result, domain)) { + result = ads_methods.enum_dom_groups(domain, mem_ctx, + num_entries, info); + } + + return result; +} + +/* List all domain groups */ +static NTSTATUS enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + NTSTATUS result; + + result = ads_methods.enum_local_groups(domain, mem_ctx, + num_entries, info); + + if (ldap_reconnect_need_retry(result, domain)) { + result = ads_methods.enum_local_groups(domain, mem_ctx, + num_entries, info); + } + + return result; +} + +/* convert a single name to a sid in a domain */ +static NTSTATUS name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + uint32_t flags, + const char **pdom_name, + struct dom_sid *sid, + enum lsa_SidType *type) +{ + NTSTATUS result; + + result = ads_methods.name_to_sid(domain, mem_ctx, domain_name, name, + flags, pdom_name, sid, type); + + if (reconnect_need_retry(result, domain)) { + result = ads_methods.name_to_sid(domain, mem_ctx, + domain_name, name, flags, + pdom_name, sid, type); + } + + return result; +} + +/* + convert a domain SID to a user or group name +*/ +static NTSTATUS sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + char **domain_name, + char **name, + enum lsa_SidType *type) +{ + NTSTATUS result; + + result = ads_methods.sid_to_name(domain, mem_ctx, sid, + domain_name, name, type); + + if (reconnect_need_retry(result, domain)) + result = ads_methods.sid_to_name(domain, mem_ctx, sid, + domain_name, name, type); + + return result; +} + +static NTSTATUS rids_to_names(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + uint32_t *rids, + size_t num_rids, + char **domain_name, + char ***names, + enum lsa_SidType **types) +{ + NTSTATUS result; + + result = ads_methods.rids_to_names(domain, mem_ctx, sid, + rids, num_rids, + domain_name, names, types); + if (reconnect_need_retry(result, domain)) { + result = ads_methods.rids_to_names(domain, mem_ctx, sid, + rids, num_rids, domain_name, + names, types); + } + + return result; +} + +/* Lookup groups a user is a member of. I wish Unix had a call like this! */ +static NTSTATUS lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *num_groups, + struct dom_sid **user_gids) +{ + NTSTATUS result; + + result = ads_methods.lookup_usergroups(domain, mem_ctx, user_sid, + num_groups, user_gids); + + if (ldap_reconnect_need_retry(result, domain)) { + result = ads_methods.lookup_usergroups(domain, mem_ctx, + user_sid, num_groups, + user_gids); + } + + return result; +} + +static NTSTATUS lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *num_aliases, uint32_t **alias_rids) +{ + NTSTATUS result; + + result = ads_methods.lookup_useraliases(domain, mem_ctx, num_sids, sids, + num_aliases, alias_rids); + + if (reconnect_need_retry(result, domain)) { + result = ads_methods.lookup_useraliases(domain, mem_ctx, + num_sids, sids, + num_aliases, + alias_rids); + } + + return result; +} + +/* Lookup group membership given a rid. */ +static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem, char ***names, + uint32_t **name_types) +{ + NTSTATUS result; + + result = ads_methods.lookup_groupmem(domain, mem_ctx, group_sid, type, + num_names, sid_mem, names, + name_types); + + if (ldap_reconnect_need_retry(result, domain)) { + result = ads_methods.lookup_groupmem(domain, mem_ctx, group_sid, + type, num_names, sid_mem, + names, name_types); + } + + return result; +} + +static NTSTATUS lookup_aliasmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *num_names, + struct dom_sid **sid_mem) +{ + NTSTATUS result = NT_STATUS_OK; + + result = ads_methods.lookup_aliasmem(domain, + mem_ctx, + group_sid, + type, + num_names, + sid_mem); + + if (ldap_reconnect_need_retry(result, domain)) { + result = ads_methods.lookup_aliasmem(domain, + mem_ctx, + group_sid, + type, + num_names, + sid_mem); + } + return result; +} + +/* find the lockout policy of a domain */ +static NTSTATUS lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *policy) +{ + NTSTATUS result; + + result = ads_methods.lockout_policy(domain, mem_ctx, policy); + + if (reconnect_need_retry(result, domain)) { + result = ads_methods.lockout_policy(domain, mem_ctx, policy); + } + + return result; +} + +/* find the password policy of a domain */ +static NTSTATUS password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *policy) +{ + NTSTATUS result; + + result = ads_methods.password_policy(domain, mem_ctx, policy); + + if (reconnect_need_retry(result, domain)) { + result = ads_methods.password_policy(domain, mem_ctx, policy); + } + + return result; +} + +/* get a list of trusted domains */ +static NTSTATUS trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *trusts) +{ + NTSTATUS result; + + result = ads_methods.trusted_domains(domain, mem_ctx, trusts); + + if (reconnect_need_retry(result, domain)) { + result = ads_methods.trusted_domains(domain, mem_ctx, trusts); + } + + return result; +} + +/* the rpc backend methods are exposed via this structure */ +struct winbindd_methods reconnect_ads_methods = { + true, + query_user_list, + enum_dom_groups, + enum_local_groups, + name_to_sid, + sid_to_name, + rids_to_names, + lookup_usergroups, + lookup_useraliases, + lookup_groupmem, + lookup_aliasmem, + lockout_policy, + password_policy, + trusted_domains, +}; + +#endif diff --git a/source3/winbindd/winbindd_rpc.c b/source3/winbindd/winbindd_rpc.c new file mode 100644 index 0000000..2b4a47e --- /dev/null +++ b/source3/winbindd/winbindd_rpc.c @@ -0,0 +1,855 @@ +/* + * Unix SMB/CIFS implementation. + * + * Winbind rpc backend functions + * + * Copyright (c) 2000-2003 Tim Potter + * Copyright (c) 2001 Andrew Tridgell + * Copyright (c) 2005 Volker Lendecke + * Copyright (c) 2008 Guenther Deschner (pidl conversion) + * Copyright (c) 2010 Andreas Schneider <asn@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.h" +#include "winbindd_rpc.h" +#include "rpc_client/rpc_client.h" +#include "librpc/gen_ndr/ndr_samr_c.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" +#include "rpc_client/cli_samr.h" +#include "rpc_client/cli_lsarpc.h" +#include "../libcli/security/security.h" +#include "lsa.h" + +/* Query display info for a domain */ +NTSTATUS rpc_query_user_list(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + const struct dom_sid *domain_sid, + uint32_t **prids) +{ + struct dcerpc_binding_handle *b = samr_pipe->binding_handle; + uint32_t *rids = NULL; + uint32_t num_rids = 0; + uint32_t i = 0; + uint32_t resume_handle = 0; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + TALLOC_CTX *tmp_ctx; + + *prids = NULL; + + tmp_ctx = talloc_stackframe(); + if (tmp_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + do { + struct samr_SamArray *sam_array = NULL; + uint32_t count = 0; + uint32_t *tmp; + + status = dcerpc_samr_EnumDomainUsers( + b, tmp_ctx, samr_policy, &resume_handle, + ACB_NORMAL, &sam_array, 0xffff, &count, &result); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + if (!NT_STATUS_IS_OK(result)) { + if (!NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) { + DBG_WARNING("EnumDomainUsers failed: %s\n", + nt_errstr(result)); + status = result; + goto done; + } + } + + if (num_rids + count < num_rids) { + status = NT_STATUS_INTEGER_OVERFLOW; + goto done; + } + + tmp = talloc_realloc(tmp_ctx, rids, uint32_t, num_rids+count); + if (tmp == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + rids = tmp; + + for (i=0; i<count; i++) { + rids[num_rids++] = sam_array->entries[i].idx; + } + + TALLOC_FREE(sam_array); + } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)); + + *prids = talloc_steal(mem_ctx, rids); + status = NT_STATUS_OK; + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* List all domain groups */ +NTSTATUS rpc_enum_dom_groups(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + uint32_t *pnum_info, + struct wb_acct_info **pinfo) +{ + struct wb_acct_info *info = NULL; + uint32_t start = 0; + uint32_t num_info = 0; + NTSTATUS status, result; + struct dcerpc_binding_handle *b = samr_pipe->binding_handle; + + *pnum_info = 0; + + do { + struct samr_SamArray *sam_array = NULL; + uint32_t count = 0; + uint32_t g; + + /* start is updated by this call. */ + status = dcerpc_samr_EnumDomainGroups(b, + mem_ctx, + samr_policy, + &start, + &sam_array, + 0xFFFF, /* buffer size? */ + &count, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + if (!NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) { + DEBUG(2,("query_user_list: failed to enum domain groups: %s\n", + nt_errstr(result))); + return result; + } + } + + info = talloc_realloc(mem_ctx, + info, + struct wb_acct_info, + num_info + count); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (g = 0; g < count; g++) { + struct wb_acct_info *i = &info[num_info + g]; + + i->acct_name = talloc_strdup(info, + sam_array->entries[g].name.string); + if (i->acct_name == NULL) { + TALLOC_FREE(info); + return NT_STATUS_NO_MEMORY; + } + i->acct_desc = NULL; + i->rid = sam_array->entries[g].idx; + } + + num_info += count; + } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)); + + *pnum_info = num_info; + *pinfo = info; + + return NT_STATUS_OK; +} + +NTSTATUS rpc_enum_local_groups(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + uint32_t *pnum_info, + struct wb_acct_info **pinfo) +{ + struct wb_acct_info *info = NULL; + uint32_t num_info = 0; + NTSTATUS status, result; + struct dcerpc_binding_handle *b = samr_pipe->binding_handle; + + *pnum_info = 0; + + do { + struct samr_SamArray *sam_array = NULL; + uint32_t count = 0; + uint32_t start = num_info; + uint32_t g; + + status = dcerpc_samr_EnumDomainAliases(b, + mem_ctx, + samr_policy, + &start, + &sam_array, + 0xFFFF, /* buffer size? */ + &count, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + if (!NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) { + return result; + } + } + + info = talloc_realloc(mem_ctx, + info, + struct wb_acct_info, + num_info + count); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (g = 0; g < count; g++) { + struct wb_acct_info *i = &info[num_info + g]; + + i->acct_name = talloc_strdup(info, + sam_array->entries[g].name.string); + if (i->acct_name == NULL) { + TALLOC_FREE(info); + return NT_STATUS_NO_MEMORY; + } + i->acct_desc = NULL; + i->rid = sam_array->entries[g].idx; + } + + num_info += count; + } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)); + + *pnum_info = num_info; + *pinfo = info; + + return NT_STATUS_OK; +} + +/* Lookup groups a user is a member of. */ +NTSTATUS rpc_lookup_usergroups(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + const struct dom_sid *domain_sid, + const struct dom_sid *user_sid, + uint32_t *pnum_groups, + struct dom_sid **puser_grpsids) +{ + struct policy_handle user_policy; + struct samr_RidWithAttributeArray *rid_array = NULL; + struct dom_sid *user_grpsids = NULL; + uint32_t num_groups = 0, i; + uint32_t user_rid; + NTSTATUS status, result; + struct dcerpc_binding_handle *b = samr_pipe->binding_handle; + + if (!sid_peek_check_rid(domain_sid, user_sid, &user_rid)) { + return NT_STATUS_UNSUCCESSFUL; + } + + /* Get user handle */ + status = dcerpc_samr_OpenUser(b, + mem_ctx, + samr_policy, + SEC_FLAG_MAXIMUM_ALLOWED, + user_rid, + &user_policy, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + /* Query user rids */ + status = dcerpc_samr_GetGroupsForUser(b, + mem_ctx, + &user_policy, + &rid_array, + &result); + { + NTSTATUS _result; + dcerpc_samr_Close(b, mem_ctx, &user_policy, &_result); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + num_groups = rid_array->count; + + user_grpsids = talloc_array(mem_ctx, struct dom_sid, num_groups); + if (user_grpsids == NULL) { + status = NT_STATUS_NO_MEMORY; + return status; + } + + for (i = 0; i < num_groups; i++) { + sid_compose(&(user_grpsids[i]), domain_sid, + rid_array->rids[i].rid); + } + + *pnum_groups = num_groups; + + *puser_grpsids = user_grpsids; + + return NT_STATUS_OK; +} + +NTSTATUS rpc_lookup_useraliases(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *pnum_aliases, + uint32_t **palias_rids) +{ +#define MAX_SAM_ENTRIES_W2K 0x400 /* 1024 */ + uint32_t num_query_sids = 0; + uint32_t num_queries = 1; + uint32_t num_aliases = 0; + uint32_t total_sids = 0; + uint32_t *alias_rids = NULL; + uint32_t rangesize = MAX_SAM_ENTRIES_W2K; + uint32_t i; + struct samr_Ids alias_rids_query; + NTSTATUS status, result; + struct dcerpc_binding_handle *b = samr_pipe->binding_handle; + + do { + /* prepare query */ + struct lsa_SidArray sid_array; + + ZERO_STRUCT(sid_array); + + num_query_sids = MIN(num_sids - total_sids, rangesize); + + DEBUG(10,("rpc: lookup_useraliases: entering query %d for %d sids\n", + num_queries, num_query_sids)); + + if (num_query_sids) { + sid_array.sids = talloc_zero_array(mem_ctx, struct lsa_SidPtr, num_query_sids); + if (sid_array.sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + } else { + sid_array.sids = NULL; + } + + for (i = 0; i < num_query_sids; i++) { + sid_array.sids[i].sid = dom_sid_dup(mem_ctx, &sids[total_sids++]); + if (sid_array.sids[i].sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + sid_array.num_sids = num_query_sids; + + /* do request */ + status = dcerpc_samr_GetAliasMembership(b, + mem_ctx, + samr_policy, + &sid_array, + &alias_rids_query, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + /* process output */ + for (i = 0; i < alias_rids_query.count; i++) { + size_t na = num_aliases; + + if (!add_rid_to_array_unique(mem_ctx, + alias_rids_query.ids[i], + &alias_rids, + &na)) { + return NT_STATUS_NO_MEMORY; + } + num_aliases = na; + } + + num_queries++; + + } while (total_sids < num_sids); + + DEBUG(10,("rpc: rpc_lookup_useraliases: got %d aliases in %d queries " + "(rangesize: %d)\n", num_aliases, num_queries, rangesize)); + + *pnum_aliases = num_aliases; + *palias_rids = alias_rids; + + return NT_STATUS_OK; +#undef MAX_SAM_ENTRIES_W2K +} + +/* Lookup group membership given a rid. */ +NTSTATUS rpc_lookup_groupmem(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + const char *domain_name, + const struct dom_sid *domain_sid, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *pnum_names, + struct dom_sid **psid_mem, + char ***pnames, + uint32_t **pname_types) +{ + struct policy_handle group_policy; + uint32_t group_rid; + uint32_t *rid_mem = NULL; + + uint32_t num_names = 0; + uint32_t total_names = 0; + struct dom_sid *sid_mem = NULL; + char **names = NULL; + uint32_t *name_types = NULL; + + struct lsa_Strings tmp_names; + struct samr_Ids tmp_types; + + uint32_t j, r; + NTSTATUS status, result; + struct dcerpc_binding_handle *b = samr_pipe->binding_handle; + + if (!sid_peek_check_rid(domain_sid, group_sid, &group_rid)) { + return NT_STATUS_UNSUCCESSFUL; + } + + switch(type) { + case SID_NAME_DOM_GRP: + { + struct samr_RidAttrArray *rids = NULL; + + status = dcerpc_samr_OpenGroup(b, + mem_ctx, + samr_policy, + SEC_FLAG_MAXIMUM_ALLOWED, + group_rid, + &group_policy, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + /* + * Step #1: Get a list of user rids that are the members of the group. + */ + status = dcerpc_samr_QueryGroupMember(b, + mem_ctx, + &group_policy, + &rids, + &result); + { + NTSTATUS _result; + dcerpc_samr_Close(b, mem_ctx, &group_policy, &_result); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + + if (rids == NULL || rids->count == 0) { + pnum_names = 0; + pnames = NULL; + pname_types = NULL; + psid_mem = NULL; + + return NT_STATUS_OK; + } + + num_names = rids->count; + rid_mem = rids->rids; + + break; + } + default: + return NT_STATUS_UNSUCCESSFUL; + } + + /* + * Step #2: Convert list of rids into list of usernames. + */ + if (num_names > 0) { + names = talloc_zero_array(mem_ctx, char *, num_names); + name_types = talloc_zero_array(mem_ctx, uint32_t, num_names); + sid_mem = talloc_zero_array(mem_ctx, struct dom_sid, num_names); + if (names == NULL || name_types == NULL || sid_mem == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + for (j = 0; j < num_names; j++) { + sid_compose(&sid_mem[j], domain_sid, rid_mem[j]); + } + + status = dcerpc_samr_LookupRids(b, + mem_ctx, + samr_policy, + num_names, + rid_mem, + &tmp_names, + &tmp_types, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!NT_STATUS_IS_OK(result)) { + if (!NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) { + return result; + } + } + + /* Copy result into array. The talloc system will take + care of freeing the temporary arrays later on. */ + if (tmp_names.count != num_names) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + if (tmp_types.count != num_names) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + for (r = 0; r < tmp_names.count; r++) { + if (tmp_types.ids[r] == SID_NAME_UNKNOWN) { + continue; + } + if (total_names >= num_names) { + break; + } + names[total_names] = fill_domain_username_talloc(names, + domain_name, + tmp_names.names[r].string, + true); + if (names[total_names] == NULL) { + return NT_STATUS_NO_MEMORY; + } + name_types[total_names] = tmp_types.ids[r]; + total_names++; + } + + *pnum_names = total_names; + *pnames = names; + *pname_types = name_types; + *psid_mem = sid_mem; + + return NT_STATUS_OK; +} + +/* Lookup alias membership using a rid taken from alias_sid. */ +NTSTATUS rpc_lookup_aliasmem(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + const struct dom_sid *domain_sid, + const struct dom_sid *alias_sid, + enum lsa_SidType type, + uint32_t *pnum_sids, + struct dom_sid **psids) +{ + uint32_t alias_rid; + struct dom_sid *sid_mem = NULL; + struct lsa_SidArray sid_array; + uint32_t i; + NTSTATUS status, result; + struct dcerpc_binding_handle *b = samr_pipe->binding_handle; + + if (!sid_peek_check_rid(domain_sid, alias_sid, &alias_rid)) { + return NT_STATUS_UNSUCCESSFUL; + } + + switch (type) { + case SID_NAME_ALIAS: { + struct policy_handle alias_policy; + + status = dcerpc_samr_OpenAlias(b, + mem_ctx, + samr_policy, + SEC_FLAG_MAXIMUM_ALLOWED, + alias_rid, + &alias_policy, + &result); + if (any_nt_status_not_ok(status, result, &status)) { + return status; + } + + status = dcerpc_samr_GetMembersInAlias(b, + mem_ctx, + &alias_policy, + &sid_array, + &result); + { + NTSTATUS _result; + dcerpc_samr_Close(b, mem_ctx, &alias_policy, &_result); + } + if (any_nt_status_not_ok(status, result, &status)) { + return status; + } + + sid_mem = talloc_zero_array(mem_ctx, + struct dom_sid, + sid_array.num_sids); + if (sid_mem == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * We cannot just simply assign '*psids = sid_array.sids;' + * we need to copy every sid since these are incompatible types: + * 'struct dom_sid *' vs 'struct lsa_SidPtr *' + */ + for (i = 0; i < sid_array.num_sids; i++) { + sid_copy(&sid_mem[i], sid_array.sids[i].sid); + } + + *pnum_sids = sid_array.num_sids; + *psids = sid_mem; + + return NT_STATUS_OK; + } + default: + return NT_STATUS_UNSUCCESSFUL; + } +} + +/* Get a list of trusted domains */ +NTSTATUS rpc_trusted_domains(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *lsa_pipe, + struct policy_handle *lsa_policy, + uint32_t *pnum_trusts, + struct netr_DomainTrust **ptrusts) +{ + struct netr_DomainTrust *array = NULL; + uint32_t enum_ctx = 0; + uint32_t count = 0; + NTSTATUS status, result; + struct dcerpc_binding_handle *b = lsa_pipe->binding_handle; + + do { + struct lsa_DomainList dom_list; + struct lsa_DomainListEx dom_list_ex; + bool has_ex = false; + uint32_t i; + + /* + * We don't run into deadlocks here, cause winbind_off() is + * called in the main function. + */ + status = dcerpc_lsa_EnumTrustedDomainsEx(b, + mem_ctx, + lsa_policy, + &enum_ctx, + &dom_list_ex, + (uint32_t) -1, + &result); + if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_ERR(result) && + dom_list_ex.count > 0) { + count += dom_list_ex.count; + has_ex = true; + } else { + status = dcerpc_lsa_EnumTrustDom(b, + mem_ctx, + lsa_policy, + &enum_ctx, + &dom_list, + (uint32_t) -1, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + if (!NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) { + return result; + } + } + + count += dom_list.count; + } + + array = talloc_realloc(mem_ctx, + array, + struct netr_DomainTrust, + count); + if (array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < count; i++) { + struct netr_DomainTrust *trust = &array[i]; + struct dom_sid *sid; + + ZERO_STRUCTP(trust); + + sid = talloc(array, struct dom_sid); + if (sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (dom_list_ex.domains[i].sid == NULL) { + DBG_ERR("Trusted domain %s has no SID, " + "skipping!\n", + trust->dns_name); + continue; + } + + if (has_ex) { + trust->netbios_name = talloc_move(array, + &dom_list_ex.domains[i].netbios_name.string); + trust->dns_name = talloc_move(array, + &dom_list_ex.domains[i].domain_name.string); + sid_copy(sid, dom_list_ex.domains[i].sid); + } else { + trust->netbios_name = talloc_move(array, + &dom_list.domains[i].name.string); + trust->dns_name = NULL; + + sid_copy(sid, dom_list.domains[i].sid); + } + + trust->sid = sid; + } + } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)); + + *pnum_trusts = count; + *ptrusts = array; + + return NT_STATUS_OK; +} + +static NTSTATUS rpc_try_lookup_sids3(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + struct rpc_pipe_client *cli, + struct lsa_SidArray *sids, + struct lsa_RefDomainList **pdomains, + struct lsa_TransNameArray **pnames) +{ + struct lsa_TransNameArray2 lsa_names2; + struct lsa_TransNameArray *names = *pnames; + uint32_t i, count = 0; + NTSTATUS status, result; + + ZERO_STRUCT(lsa_names2); + status = dcerpc_lsa_LookupSids3(cli->binding_handle, + mem_ctx, + sids, + pdomains, + &lsa_names2, + LSA_LOOKUP_NAMES_ALL, + &count, + LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES, + LSA_CLIENT_REVISION_2, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (NT_STATUS_LOOKUP_ERR(result)) { + return result; + } + if (sids->num_sids != lsa_names2.count) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + names->count = lsa_names2.count; + names->names = talloc_array(names, struct lsa_TranslatedName, + names->count); + if (names->names == NULL) { + return NT_STATUS_NO_MEMORY; + } + for (i=0; i<names->count; i++) { + names->names[i].sid_type = lsa_names2.names[i].sid_type; + names->names[i].name.string = talloc_move( + names->names, &lsa_names2.names[i].name.string); + names->names[i].sid_index = lsa_names2.names[i].sid_index; + + if (names->names[i].sid_index == UINT32_MAX) { + continue; + } + if ((*pdomains) == NULL) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + if (names->names[i].sid_index >= (*pdomains)->count) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + } + return NT_STATUS_OK; +} + +NTSTATUS rpc_lookup_sids(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + struct lsa_SidArray *sids, + struct lsa_RefDomainList **pdomains, + struct lsa_TransNameArray **pnames) +{ + struct lsa_TransNameArray *names = *pnames; + struct rpc_pipe_client *cli = NULL; + struct policy_handle lsa_policy; + uint32_t count; + uint32_t i; + NTSTATUS status, result; + + status = cm_connect_lsat(domain, mem_ctx, &cli, &lsa_policy); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (cli->transport->transport == NCACN_IP_TCP) { + return rpc_try_lookup_sids3(mem_ctx, domain, cli, sids, + pdomains, pnames); + } + + status = dcerpc_lsa_LookupSids(cli->binding_handle, mem_ctx, + &lsa_policy, sids, pdomains, + names, LSA_LOOKUP_NAMES_ALL, + &count, &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (NT_STATUS_LOOKUP_ERR(result)) { + return result; + } + + if (sids->num_sids != names->count) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + for (i=0; i < names->count; i++) { + if (names->names[i].sid_index == UINT32_MAX) { + continue; + } + if ((*pdomains) == NULL) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + if (names->names[i].sid_index >= (*pdomains)->count) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + } + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_rpc.h b/source3/winbindd/winbindd_rpc.h new file mode 100644 index 0000000..020958f --- /dev/null +++ b/source3/winbindd/winbindd_rpc.h @@ -0,0 +1,95 @@ +/* + * Unix SMB/CIFS implementation. + * + * Winbind rpc backend functions + * + * Copyright (c) 2000-2003 Tim Potter + * Copyright (c) 2001 Andrew Tridgell + * Copyright (c) 2005 Volker Lendecke + * Copyright (c) 2008 Guenther Deschner (pidl conversion) + * Copyright (c) 2010 Andreas Schneider <asn@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 _WINBINDD_RPC_H_ +#define _WINBINDD_RPC_H_ + +/* Query display info for a domain */ +NTSTATUS rpc_query_user_list(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + const struct dom_sid *domain_sid, + uint32_t **prids); + +NTSTATUS rpc_enum_dom_groups(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *sam_policy, + uint32_t *pnum_info, + struct wb_acct_info **pinfo); + +/* List all domain groups */ +NTSTATUS rpc_enum_local_groups(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + uint32_t *pnum_info, + struct wb_acct_info **pinfo); + +/* Lookup groups a user is a member of. */ +NTSTATUS rpc_lookup_usergroups(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + const struct dom_sid *domain_sid, + const struct dom_sid *user_sid, + uint32_t *pnum_groups, + struct dom_sid **puser_grpsids); + +NTSTATUS rpc_lookup_useraliases(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *pnum_aliases, + uint32_t **palias_rids); + +/* Lookup group membership given a rid. */ +NTSTATUS rpc_lookup_groupmem(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + const char *domain_name, + const struct dom_sid *domain_sid, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *pnum_names, + struct dom_sid **psid_mem, + char ***pnames, + uint32_t **pname_types); + +NTSTATUS rpc_lookup_aliasmem(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *samr_pipe, + struct policy_handle *samr_policy, + const struct dom_sid *domain_sid, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *pnum_sids, + struct dom_sid **psids); + +/* Get a list of trusted domains */ +NTSTATUS rpc_trusted_domains(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client *lsa_pipe, + struct policy_handle *lsa_policy, + uint32_t *pnum_trusts, + struct netr_DomainTrust **ptrusts); + +#endif /* _WINBINDD_RPC_H_ */ diff --git a/source3/winbindd/winbindd_samr.c b/source3/winbindd/winbindd_samr.c new file mode 100644 index 0000000..65e9932 --- /dev/null +++ b/source3/winbindd/winbindd_samr.c @@ -0,0 +1,1424 @@ +/* + * Unix SMB/CIFS implementation. + * + * Winbind rpc backend functions + * + * Copyright (c) 2000-2003 Tim Potter + * Copyright (c) 2001 Andrew Tridgell + * Copyright (c) 2005 Volker Lendecke + * Copyright (c) 2008 Guenther Deschner (pidl conversion) + * Copyright (c) 2010 Andreas Schneider <asn@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.h" +#include "winbindd_rpc.h" +#include "lib/util_unixsids.h" +#include "rpc_client/rpc_client.h" +#include "rpc_client/cli_pipe.h" +#include "../librpc/gen_ndr/ndr_samr_c.h" +#include "rpc_client/cli_samr.h" +#include "../librpc/gen_ndr/ndr_lsa_c.h" +#include "rpc_client/cli_lsarpc.h" +#include "rpc_server/rpc_ncacn_np.h" +#include "../libcli/security/security.h" +#include "passdb/machine_sid.h" +#include "auth.h" +#include "source3/lib/global_contexts.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/* + * The other end of this won't go away easily, so we can trust it + * + * It is either a long-lived process with the same lifetime as + * winbindd or a part of this process + */ +struct winbind_internal_pipes { + struct tevent_timer *shutdown_timer; + struct rpc_pipe_client *samr_pipe; + struct policy_handle samr_domain_hnd; + struct rpc_pipe_client *lsa_pipe; + struct policy_handle lsa_hnd; +}; + + +NTSTATUS open_internal_samr_conn(TALLOC_CTX *mem_ctx, + struct winbindd_domain *domain, + struct rpc_pipe_client **samr_pipe, + struct policy_handle *samr_domain_hnd) +{ + NTSTATUS status, result; + struct policy_handle samr_connect_hnd; + struct dcerpc_binding_handle *b; + + status = wb_open_internal_pipe(mem_ctx, &ndr_table_samr, samr_pipe); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Could not connect to %s pipe: %s\n", + ndr_table_samr.name, nt_errstr(status)); + return status; + } + + b = (*samr_pipe)->binding_handle; + + status = dcerpc_samr_Connect2(b, mem_ctx, + (*samr_pipe)->desthost, + SEC_FLAG_MAXIMUM_ALLOWED, + &samr_connect_hnd, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + status = dcerpc_samr_OpenDomain(b, mem_ctx, + &samr_connect_hnd, + SEC_FLAG_MAXIMUM_ALLOWED, + &domain->sid, + samr_domain_hnd, + &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return result; +} + +NTSTATUS open_internal_lsa_conn(TALLOC_CTX *mem_ctx, + struct rpc_pipe_client **lsa_pipe, + struct policy_handle *lsa_hnd) +{ + NTSTATUS status; + + status = wb_open_internal_pipe(mem_ctx, &ndr_table_lsarpc, lsa_pipe); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Could not connect to %s pipe: %s\n", + ndr_table_lsarpc.name, nt_errstr(status)); + return status; + } + + status = rpccli_lsa_open_policy((*lsa_pipe), + mem_ctx, + true, + SEC_FLAG_MAXIMUM_ALLOWED, + lsa_hnd); + + return status; +} + +static void cached_internal_pipe_close( + struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct winbindd_domain *domain = talloc_get_type_abort( + private_data, struct winbindd_domain); + /* + * Freeing samr_pipes closes the cached pipes. + * + * We can do a hard close because at the time of this commit + * we only use synchronous calls to external pipes. So we can't + * have any outstanding requests. Also, we don't set + * dcerpc_binding_handle_set_sync_ev in winbind, so we don't + * get nested event loops. Once we start to get async in + * winbind children, we need to check for outstanding calls + */ + TALLOC_FREE(domain->backend_data.samr_pipes); +} + +static NTSTATUS open_cached_internal_pipe_conn( + struct winbindd_domain *domain, + struct rpc_pipe_client **samr_pipe, + struct policy_handle *samr_domain_hnd, + struct rpc_pipe_client **lsa_pipe, + struct policy_handle *lsa_hnd) +{ + struct winbind_internal_pipes *internal_pipes = + domain->backend_data.samr_pipes; + + if (internal_pipes == NULL) { + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + + internal_pipes = talloc_zero(frame, + struct winbind_internal_pipes); + + status = open_internal_samr_conn( + internal_pipes, + domain, + &internal_pipes->samr_pipe, + &internal_pipes->samr_domain_hnd); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + status = open_internal_lsa_conn(internal_pipes, + &internal_pipes->lsa_pipe, + &internal_pipes->lsa_hnd); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + internal_pipes->shutdown_timer = tevent_add_timer( + global_event_context(), + internal_pipes, + timeval_current_ofs(5, 0), + cached_internal_pipe_close, + domain); + if (internal_pipes->shutdown_timer == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + domain->backend_data.samr_pipes = + talloc_steal(domain, internal_pipes); + + TALLOC_FREE(frame); + } + + if (samr_domain_hnd) { + *samr_domain_hnd = internal_pipes->samr_domain_hnd; + } + + if (samr_pipe) { + *samr_pipe = internal_pipes->samr_pipe; + } + + if (lsa_hnd) { + *lsa_hnd = internal_pipes->lsa_hnd; + } + + if (lsa_pipe) { + *lsa_pipe = internal_pipes->lsa_pipe; + } + + tevent_update_timer( + internal_pipes->shutdown_timer, + timeval_current_ofs(5, 0)); + + return NT_STATUS_OK; +} + +static bool reset_connection_on_error(struct winbindd_domain *domain, + struct rpc_pipe_client *p, + NTSTATUS status) +{ + struct dcerpc_binding_handle *b = p->binding_handle; + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR)) + { + TALLOC_FREE(domain->backend_data.samr_pipes); + return true; + } + + if (!dcerpc_binding_handle_is_connected(b)) { + TALLOC_FREE(domain->backend_data.samr_pipes); + return true; + } + + return false; +} + +/********************************************************************* + SAM specific functions. +*********************************************************************/ + +/* List all domain groups */ +static NTSTATUS sam_enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *pnum_info, + struct wb_acct_info **pinfo) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol = { 0 }; + struct wb_acct_info *info = NULL; + uint32_t num_info = 0; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + bool retry = false; + + DEBUG(3,("sam_enum_dom_groups\n")); + + if (pnum_info) { + *pnum_info = 0; + } + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + return status; + } + + status = rpc_enum_dom_groups(tmp_ctx, + samr_pipe, + &dom_pol, + &num_info, + &info); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + return status; + } + + if (pnum_info) { + *pnum_info = num_info; + } + + if (pinfo) { + *pinfo = talloc_move(mem_ctx, &info); + } + + TALLOC_FREE(tmp_ctx); + return status; +} + +/* Query display info for a domain */ +static NTSTATUS sam_query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **prids) +{ + struct rpc_pipe_client *samr_pipe = NULL; + struct policy_handle dom_pol = { 0 }; + uint32_t *rids = NULL; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + bool retry = false; + + DEBUG(3,("samr_query_user_list\n")); + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_query_user_list(tmp_ctx, + samr_pipe, + &dom_pol, + &domain->sid, + &rids); + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (prids != NULL) { + *prids = talloc_move(mem_ctx, &rids); + } + +done: + TALLOC_FREE(rids); + TALLOC_FREE(tmp_ctx); + return status; +} + +/* get a list of trusted domains - builtin domain */ +static NTSTATUS sam_trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *ptrust_list) +{ + struct rpc_pipe_client *lsa_pipe; + struct policy_handle lsa_policy = { 0 }; + struct netr_DomainTrust *trusts = NULL; + uint32_t num_trusts = 0; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + bool retry = false; + + DEBUG(3,("samr: trusted domains\n")); + + if (ptrust_list) { + ZERO_STRUCTP(ptrust_list); + } + +again: + status = open_cached_internal_pipe_conn(domain, + NULL, + NULL, + &lsa_pipe, + &lsa_policy); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_trusted_domains(tmp_ctx, + lsa_pipe, + &lsa_policy, + &num_trusts, + &trusts); + + if (!retry && reset_connection_on_error(domain, lsa_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (ptrust_list) { + ptrust_list->count = num_trusts; + ptrust_list->array = talloc_move(mem_ctx, &trusts); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* Lookup group membership given a rid. */ +static NTSTATUS sam_lookup_groupmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *pnum_names, + struct dom_sid **psid_mem, + char ***pnames, + uint32_t **pname_types) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol = { 0 }; + + uint32_t num_names = 0; + struct dom_sid *sid_mem = NULL; + char **names = NULL; + uint32_t *name_types = NULL; + + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + bool retry = false; + + DEBUG(3,("sam_lookup_groupmem\n")); + + /* Paranoia check */ + if (sid_check_is_in_builtin(group_sid) && (type != SID_NAME_ALIAS)) { + /* There's no groups, only aliases in BUILTIN */ + status = NT_STATUS_NO_SUCH_GROUP; + goto done; + } + + if (pnum_names) { + *pnum_names = 0; + } + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_lookup_groupmem(tmp_ctx, + samr_pipe, + &dom_pol, + domain->name, + &domain->sid, + group_sid, + type, + &num_names, + &sid_mem, + &names, + &name_types); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (pnum_names) { + *pnum_names = num_names; + } + + if (pnames) { + *pnames = talloc_move(mem_ctx, &names); + } + + if (pname_types) { + *pname_types = talloc_move(mem_ctx, &name_types); + } + + if (psid_mem) { + *psid_mem = talloc_move(mem_ctx, &sid_mem); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* Lookup alias membership */ +static NTSTATUS sam_lookup_aliasmem(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *group_sid, + enum lsa_SidType type, + uint32_t *pnum_sids, + struct dom_sid **psid_mem) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol = {0}; + + uint32_t num_sids = 0; + struct dom_sid *sid_mem = NULL; + + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + bool retry = false; + + DBG_INFO("sam_lookup_aliasmem\n"); + + /* Paranoia check */ + if (type != SID_NAME_ALIAS) { + status = NT_STATUS_NO_SUCH_ALIAS; + goto done; + } + + if (pnum_sids) { + *pnum_sids = 0; + } + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_lookup_aliasmem(tmp_ctx, + samr_pipe, + &dom_pol, + &domain->sid, + group_sid, + type, + &num_sids, + &sid_mem); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (pnum_sids) { + *pnum_sids = num_sids; + } + + if (psid_mem) { + *psid_mem = talloc_move(mem_ctx, &sid_mem); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/********************************************************************* + BUILTIN specific functions. +*********************************************************************/ + +/* List all domain groups */ +static NTSTATUS builtin_enum_dom_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *num_entries, + struct wb_acct_info **info) +{ + /* BUILTIN doesn't have domain groups */ + *num_entries = 0; + *info = NULL; + return NT_STATUS_OK; +} + +/* Query display info for a domain */ +static NTSTATUS builtin_query_user_list(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t **rids) +{ + /* We don't have users */ + *rids = NULL; + return NT_STATUS_OK; +} + +/* get a list of trusted domains - builtin domain */ +static NTSTATUS builtin_trusted_domains(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct netr_DomainTrustList *trusts) +{ + ZERO_STRUCTP(trusts); + return NT_STATUS_OK; +} + +/********************************************************************* + COMMON functions. +*********************************************************************/ + +/* List all local groups (aliases) */ +static NTSTATUS sam_enum_local_groups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t *pnum_info, + struct wb_acct_info **pinfo) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol = { 0 }; + struct wb_acct_info *info = NULL; + uint32_t num_info = 0; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + bool retry = false; + + DEBUG(3,("samr: enum local groups\n")); + + if (pnum_info) { + *pnum_info = 0; + } + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_enum_local_groups(mem_ctx, + samr_pipe, + &dom_pol, + &num_info, + + &info); + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (pnum_info) { + *pnum_info = num_info; + } + + if (pinfo) { + *pinfo = talloc_move(mem_ctx, &info); + } + +done: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* convert a single name to a sid in a domain */ +static NTSTATUS sam_name_to_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + uint32_t flags, + const char **pdom_name, + struct dom_sid *psid, + enum lsa_SidType *ptype) +{ + struct rpc_pipe_client *samr_pipe = NULL; + struct dcerpc_binding_handle *h = NULL; + struct policy_handle dom_pol = { .handle_type = 0, }; + struct dom_sid sid; + const char *dom_name = domain_name; + struct lsa_String lsa_name = { .string = name }; + struct samr_Ids rids = { .count = 0 }; + struct samr_Ids types = { .count = 0 }; + enum lsa_SidType type; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status = NT_STATUS_NONE_MAPPED; + NTSTATUS result; + bool retry = false; + bool ok; + + DBG_NOTICE("%s\\%s\n", domain_name, name); + + if (strequal(domain_name, unix_users_domain_name())) { + struct passwd *pwd = NULL; + + if (name[0] == '\0') { + sid_copy(&sid, &global_sid_Unix_Users); + type = SID_NAME_DOMAIN; + goto done; + } + + pwd = Get_Pwnam_alloc(tmp_ctx, name); + if (pwd == NULL) { + goto fail; + } + ok = sid_compose(&sid, &global_sid_Unix_Users, pwd->pw_uid); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + type = SID_NAME_USER; + goto done; + } + + if (strequal(domain_name, unix_groups_domain_name())) { + struct group *grp = NULL; + + if (name[0] == '\0') { + sid_copy(&sid, &global_sid_Unix_Groups); + type = SID_NAME_DOMAIN; + goto done; + } + + grp = getgrnam(name); + if (grp == NULL) { + goto fail; + } + ok = sid_compose(&sid, &global_sid_Unix_Groups, grp->gr_gid); + if (!ok) { + status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + type = SID_NAME_DOM_GRP; + goto done; + } + + if (name[0] == '\0') { + sid_copy(&sid, &domain->sid); + type = SID_NAME_DOMAIN; + goto done; + } + + ok = lookup_wellknown_name(tmp_ctx, name, &sid, &dom_name); + if (ok) { + type = SID_NAME_WKN_GRP; + goto done; + } + + { + char *normalized = NULL; + NTSTATUS nstatus = normalize_name_unmap( + tmp_ctx, name, &normalized); + if (NT_STATUS_IS_OK(nstatus) || + NT_STATUS_EQUAL(nstatus, NT_STATUS_FILE_RENAMED)) { + lsa_name.string = normalized; + } + } + +again: + status = open_cached_internal_pipe_conn( + domain, &samr_pipe, &dom_pol, NULL, NULL); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + h = samr_pipe->binding_handle; + + status = dcerpc_samr_LookupNames( + h, tmp_ctx, &dom_pol, 1, &lsa_name, &rids, &types, &result); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_samr_LookupNames returned %s\n", + nt_errstr(status)); + goto fail; + } + if (!NT_STATUS_IS_OK(result)) { + DBG_DEBUG("dcerpc_samr_LookupNames resulted in %s\n", + nt_errstr(status)); + status = result; + goto fail; + } + + sid_compose(&sid, &domain->sid, rids.ids[0]); + type = types.ids[0]; + +done: + if (pdom_name != NULL) { + *pdom_name = talloc_strdup(mem_ctx, dom_name); + if (*pdom_name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + } + + if (psid) { + sid_copy(psid, &sid); + } + if (ptype) { + *ptype = type; + } + + status = NT_STATUS_OK; +fail: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* convert a domain SID to a user or group name */ +static NTSTATUS sam_sid_to_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *sid, + char **pdomain_name, + char **pname, + enum lsa_SidType *ptype) +{ + struct rpc_pipe_client *samr_pipe = NULL; + struct dcerpc_binding_handle *h = NULL; + struct policy_handle dom_pol = { .handle_type = 0, }; + const char *domain_name = ""; + const char *name = ""; + enum lsa_SidType type = SID_NAME_USE_NONE; + struct lsa_Strings names = { .count = 0, }; + struct samr_Ids types = { .count = 0 }; + struct dom_sid domain_sid; + uint32_t rid; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status = NT_STATUS_NONE_MAPPED; + NTSTATUS result; + bool retry = false; + bool ok; + + DEBUG(3,("sam_sid_to_name\n")); + + if (sid_check_is_unix_users(sid)) { + domain_name = unix_users_domain_name(); + type = SID_NAME_DOMAIN; + goto done; + } + if (sid_check_is_in_unix_users(sid)) { + struct passwd *pwd = NULL; + + ok = sid_peek_rid(sid, &rid); + if (!ok) { + goto fail; + } + pwd = getpwuid(rid); + if (pwd == NULL) { + goto fail; + } + + domain_name = unix_users_domain_name(); + name = talloc_strdup(tmp_ctx, pwd->pw_name); + if (name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + type = SID_NAME_USER; + goto done; + } + + if (sid_check_is_unix_groups(sid)) { + domain_name = unix_groups_domain_name(); + type = SID_NAME_DOMAIN; + goto done; + } + if (sid_check_is_in_unix_groups(sid)) { + struct group *grp = NULL; + + ok = sid_peek_rid(sid, &rid); + if (!ok) { + goto fail; + } + grp = getgrgid(rid); + if (grp == NULL) { + goto fail; + } + + domain_name = unix_groups_domain_name(); + name = talloc_strdup(tmp_ctx, grp->gr_name); + if (name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + type = SID_NAME_DOM_GRP; + goto done; + } + + ok = lookup_wellknown_sid(tmp_ctx, sid, &domain_name, &name); + if (ok) { + type = SID_NAME_WKN_GRP; + goto done; + } + + if (dom_sid_equal(sid, &domain->sid)) { + domain_name = domain->name; + type = SID_NAME_DOMAIN; + goto done; + } + + sid_copy(&domain_sid, sid); + ok = sid_split_rid(&domain_sid, &rid); + if (!ok) { + goto fail; + } + + if (!dom_sid_equal(&domain_sid, &domain->sid)) { + goto fail; + } + +again: + status = open_cached_internal_pipe_conn( + domain, &samr_pipe, &dom_pol, NULL, NULL); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + h = samr_pipe->binding_handle; + + status = dcerpc_samr_LookupRids( + h, tmp_ctx, &dom_pol, 1, &rid, &names, &types, &result); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_samr_LookupRids failed: %s\n", + nt_errstr(status)); + goto fail; + } + if (!NT_STATUS_IS_OK(result)) { + DBG_DEBUG("dcerpc_samr_LookupRids resulted in %s\n", + nt_errstr(result)); + status = result; + goto fail; + } + + domain_name = domain->name; + name = names.names[0].string; + type = types.ids[0]; + + if (name != NULL) { + char *normalized = NULL; + NTSTATUS nstatus = normalize_name_map( + tmp_ctx, domain_name, name, &normalized); + if (NT_STATUS_IS_OK(nstatus) || + NT_STATUS_EQUAL(nstatus, NT_STATUS_FILE_RENAMED)) { + name = normalized; + } + } + +done: + if (ptype) { + *ptype = type; + } + + if (pname) { + *pname = talloc_strdup(mem_ctx, name); + if (*pname == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + } + + if (pdomain_name) { + *pdomain_name = talloc_strdup(mem_ctx, domain_name); + if (*pdomain_name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + } + + status = NT_STATUS_OK; +fail: + TALLOC_FREE(tmp_ctx); + return status; +} + +static NTSTATUS sam_rids_to_names(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *domain_sid, + uint32_t *rids, + size_t num_rids, + char **pdomain_name, + char ***pnames, + enum lsa_SidType **ptypes) +{ + struct rpc_pipe_client *samr_pipe = NULL; + struct dcerpc_binding_handle *h = NULL; + struct policy_handle dom_pol = { .handle_type = 0, }; + enum lsa_SidType *types = NULL; + char **names = NULL; + const char *domain_name = NULL; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status = NT_STATUS_NO_MEMORY; + NTSTATUS result; + bool retry = false; + uint32_t i; + + DEBUG(3,("sam_rids_to_names for %s\n", domain->name)); + + types = talloc_array(tmp_ctx, enum lsa_SidType, num_rids); + if (types == NULL) { + goto fail; + } + + names = talloc_array(tmp_ctx, char *, num_rids); + if (names == NULL) { + goto fail; + } + + if (sid_check_is_unix_users(domain_sid)) { + domain_name = unix_users_domain_name(); + domain_sid = &global_sid_Unix_Users; + } + if (sid_check_is_unix_groups(domain_sid)) { + domain_name = unix_groups_domain_name(); + domain_sid = &global_sid_Unix_Groups; + } + + /* Here we're only interested in the domain name being set */ + sid_check_is_wellknown_domain(domain_sid, &domain_name); + + if (domain_name != NULL) { + uint32_t num_mapped = 0; + + /* + * Do unix users/groups and wkn in a loop. There is no + * getpwuids() call & friends anyway + */ + + for (i=0; i<num_rids; i++) { + struct dom_sid sid; + char *name = NULL; + + sid_compose(&sid, domain_sid, rids[i]); + + types[i] = SID_NAME_UNKNOWN; + names[i] = NULL; + + status = sam_sid_to_name( + domain, + tmp_ctx, + &sid, + NULL, + &name, + &types[i]); + if (NT_STATUS_IS_OK(status)) { + names[i] = talloc_move(names, &name); + num_mapped += 1; + } + } + + status = NT_STATUS_NONE_MAPPED; + if (num_mapped > 0) { + status = (num_mapped == num_rids) ? + NT_STATUS_OK : STATUS_SOME_UNMAPPED; + } + goto done; + } + + domain_name = domain->name; + +again: + status = open_cached_internal_pipe_conn( + domain, &samr_pipe, &dom_pol, NULL, NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + h = samr_pipe->binding_handle; + + /* + * Magic number 1000 comes from samr.idl + */ + + for (i = 0; i < num_rids; i += 1000) { + uint32_t num_lookup_rids = MIN(num_rids - i, 1000); + struct lsa_Strings lsa_names = { + .count = 0, + }; + struct samr_Ids samr_types = { + .count = 0, + }; + uint32_t j; + + status = dcerpc_samr_LookupRids(h, + tmp_ctx, + &dom_pol, + num_lookup_rids, + &rids[i], + &lsa_names, + &samr_types, + &result); + + if (!retry && + reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + DBG_DEBUG("dcerpc_samr_LookupRids failed: %s\n", + nt_errstr(status)); + goto fail; + } + if (!NT_STATUS_IS_OK(result) && + !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) { + DBG_DEBUG("dcerpc_samr_LookupRids resulted in %s\n", + nt_errstr(result)); + status = result; + goto fail; + } + + for (j = 0; j < num_lookup_rids; j++) { + uint32_t dst = i + j; + + types[dst] = samr_types.ids[j]; + names[dst] = talloc_move( + names, + discard_const_p(char *, + &lsa_names.names[j].string)); + if (names[dst] != NULL) { + char *normalized = NULL; + NTSTATUS nstatus = + normalize_name_map(names, + domain_name, + names[dst], + &normalized); + if (NT_STATUS_IS_OK(nstatus) || + NT_STATUS_EQUAL(nstatus, + NT_STATUS_FILE_RENAMED)) { + names[dst] = normalized; + } + } + } + + TALLOC_FREE(samr_types.ids); + TALLOC_FREE(lsa_names.names); + } + +done: + if (pdomain_name) { + *pdomain_name = talloc_strdup(mem_ctx, domain_name); + if (*pdomain_name == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + } + + if (ptypes) { + *ptypes = talloc_move(mem_ctx, &types); + } + + if (pnames) { + *pnames = talloc_move(mem_ctx, &names); + } + +fail: + TALLOC_FREE(tmp_ctx); + return status; +} + +static NTSTATUS sam_lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo12 *lockout_policy) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol = { 0 }; + union samr_DomainInfo *info = NULL; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status, result; + struct dcerpc_binding_handle *b = NULL; + bool retry = false; + + DEBUG(3,("sam_lockout_policy\n")); + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + goto error; + } + + b = samr_pipe->binding_handle; + + status = dcerpc_samr_QueryDomainInfo(b, + mem_ctx, + &dom_pol, + DomainLockoutInformation, + &info, + &result); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + goto error; + } + if (!NT_STATUS_IS_OK(result)) { + status = result; + goto error; + } + + *lockout_policy = info->info12; + +error: + TALLOC_FREE(tmp_ctx); + return status; +} + +static NTSTATUS sam_password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + struct samr_DomInfo1 *passwd_policy) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol = { 0 }; + union samr_DomainInfo *info = NULL; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status, result; + struct dcerpc_binding_handle *b = NULL; + bool retry = false; + + DEBUG(3,("sam_password_policy\n")); + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + goto error; + } + + b = samr_pipe->binding_handle; + + status = dcerpc_samr_QueryDomainInfo(b, + mem_ctx, + &dom_pol, + DomainPasswordInformation, + &info, + &result); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + goto error; + } + if (!NT_STATUS_IS_OK(result)) { + status = result; + goto error; + } + + *passwd_policy = info->info1; + +error: + TALLOC_FREE(tmp_ctx); + return status; +} + +/* Lookup groups a user is a member of. */ +static NTSTATUS sam_lookup_usergroups(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *pnum_groups, + struct dom_sid **puser_grpsids) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol; + struct dom_sid *user_grpsids = NULL; + uint32_t num_groups = 0; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + bool retry = false; + + DEBUG(3,("sam_lookup_usergroups\n")); + + ZERO_STRUCT(dom_pol); + + if (pnum_groups) { + *pnum_groups = 0; + } + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_lookup_usergroups(tmp_ctx, + samr_pipe, + &dom_pol, + &domain->sid, + user_sid, + &num_groups, + &user_grpsids); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (pnum_groups) { + *pnum_groups = num_groups; + } + + if (puser_grpsids) { + *puser_grpsids = talloc_move(mem_ctx, &user_grpsids); + } + +done: + + TALLOC_FREE(tmp_ctx); + return status; +} + +static NTSTATUS sam_lookup_useraliases(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint32_t num_sids, + const struct dom_sid *sids, + uint32_t *pnum_aliases, + uint32_t **palias_rids) +{ + struct rpc_pipe_client *samr_pipe; + struct policy_handle dom_pol = { 0 }; + uint32_t num_aliases = 0; + uint32_t *alias_rids = NULL; + TALLOC_CTX *tmp_ctx = talloc_stackframe(); + NTSTATUS status; + bool retry = false; + + DEBUG(3,("sam_lookup_useraliases\n")); + + if (pnum_aliases) { + *pnum_aliases = 0; + } + +again: + status = open_cached_internal_pipe_conn(domain, + &samr_pipe, + &dom_pol, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = rpc_lookup_useraliases(tmp_ctx, + samr_pipe, + &dom_pol, + num_sids, + sids, + &num_aliases, + &alias_rids); + + if (!retry && reset_connection_on_error(domain, samr_pipe, status)) { + retry = true; + goto again; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (pnum_aliases) { + *pnum_aliases = num_aliases; + } + + if (palias_rids) { + *palias_rids = talloc_move(mem_ctx, &alias_rids); + } + +done: + + TALLOC_FREE(tmp_ctx); + return status; +} + +/* the rpc backend methods are exposed via this structure */ +struct winbindd_methods builtin_passdb_methods = { + .consistent = false, + + .query_user_list = builtin_query_user_list, + .enum_dom_groups = builtin_enum_dom_groups, + .enum_local_groups = sam_enum_local_groups, + .name_to_sid = sam_name_to_sid, + .sid_to_name = sam_sid_to_name, + .rids_to_names = sam_rids_to_names, + .lookup_usergroups = sam_lookup_usergroups, + .lookup_useraliases = sam_lookup_useraliases, + .lookup_groupmem = sam_lookup_groupmem, + .lookup_aliasmem = sam_lookup_aliasmem, + .lockout_policy = sam_lockout_policy, + .password_policy = sam_password_policy, + .trusted_domains = builtin_trusted_domains +}; + +/* the rpc backend methods are exposed via this structure */ +struct winbindd_methods sam_passdb_methods = { + .consistent = false, + + .query_user_list = sam_query_user_list, + .enum_dom_groups = sam_enum_dom_groups, + .enum_local_groups = sam_enum_local_groups, + .name_to_sid = sam_name_to_sid, + .sid_to_name = sam_sid_to_name, + .rids_to_names = sam_rids_to_names, + .lookup_usergroups = sam_lookup_usergroups, + .lookup_useraliases = sam_lookup_useraliases, + .lookup_groupmem = sam_lookup_groupmem, + .lookup_aliasmem = sam_lookup_aliasmem, + .lockout_policy = sam_lockout_policy, + .password_policy = sam_password_policy, + .trusted_domains = sam_trusted_domains +}; diff --git a/source3/winbindd/winbindd_setgrent.c b/source3/winbindd/winbindd_setgrent.c new file mode 100644 index 0000000..f4f2498 --- /dev/null +++ b/source3/winbindd/winbindd_setgrent.c @@ -0,0 +1,67 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_SETGRENT + Copyright (C) Volker Lendecke 2009 + + 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.h" + +struct winbindd_setgrent_state { + uint8_t dummy; +}; + +struct tevent_req *winbindd_setgrent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req; + struct winbindd_setgrent_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_setgrent_state); + if (req == NULL) { + return NULL; + } + TALLOC_FREE(cli->grent_state); + + D_NOTICE("[%s (%u)] Winbind external command SETGRENT start.\n" + "winbind enum groups = %d\n", + cli->client_name, + (unsigned int)cli->pid, + lp_winbind_enum_groups()); + + if (!lp_winbind_enum_groups()) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + cli->grent_state = talloc_zero(cli, struct getgrent_state); + if (tevent_req_nomem(cli->grent_state, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +NTSTATUS winbindd_setgrent_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + D_NOTICE("Winbind external command SETGRENT end.\n"); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_setpwent.c b/source3/winbindd/winbindd_setpwent.c new file mode 100644 index 0000000..3c98e5a --- /dev/null +++ b/source3/winbindd/winbindd_setpwent.c @@ -0,0 +1,67 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_SETPWENT + Copyright (C) Volker Lendecke 2009 + + 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.h" + +struct winbindd_setpwent_state { + uint8_t dummy; +}; + +struct tevent_req *winbindd_setpwent_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req; + struct winbindd_setpwent_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_setpwent_state); + if (req == NULL) { + return NULL; + } + TALLOC_FREE(cli->pwent_state); + + D_NOTICE("[%s (%u)] Winbind external command SETPWENT start.\n" + "winbind enum users = %d\n", + cli->client_name, + (unsigned int)cli->pid, + lp_winbind_enum_users()); + + if (!lp_winbind_enum_users()) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + cli->pwent_state = talloc_zero(cli, struct getpwent_state); + if (tevent_req_nomem(cli->pwent_state, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +NTSTATUS winbindd_setpwent_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + D_NOTICE("Winbind external command SETPWENT end.\n"); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_show_sequence.c b/source3/winbindd/winbindd_show_sequence.c new file mode 100644 index 0000000..e12f476 --- /dev/null +++ b/source3/winbindd/winbindd_show_sequence.c @@ -0,0 +1,167 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_SHOW_SEQUENCE + Copyright (C) Volker Lendecke 2009 + + 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.h" + +struct winbindd_show_sequence_state { + bool one_domain; + /* One domain */ + uint32_t seqnum; + + /* All domains */ + int num_domains; + NTSTATUS *statuses; + struct winbindd_domain **domains; + uint32_t *seqnums; +}; + +static void winbindd_show_sequence_done_one(struct tevent_req *subreq); +static void winbindd_show_sequence_done_all(struct tevent_req *subreq); + +struct tevent_req *winbindd_show_sequence_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_show_sequence_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_show_sequence_state); + if (req == NULL) { + return NULL; + } + state->one_domain = false; + state->domains = NULL; + state->statuses = NULL; + state->seqnums = NULL; + + /* Ensure null termination */ + request->domain_name[sizeof(request->domain_name)-1]='\0'; + + DEBUG(3, ("show_sequence %s\n", request->domain_name)); + + if (request->domain_name[0] != '\0') { + struct winbindd_domain *domain; + + state->one_domain = true; + + domain = find_domain_from_name_noinit( + request->domain_name); + if (domain == NULL) { + tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN); + return tevent_req_post(req, ev); + } + + subreq = wb_seqnum_send(state, ev, domain); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, winbindd_show_sequence_done_one, req); + return req; + } + + subreq = wb_seqnums_send(state, ev); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_show_sequence_done_all, req); + return req; +} + +static void winbindd_show_sequence_done_one(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_show_sequence_state *state = tevent_req_data( + req, struct winbindd_show_sequence_state); + NTSTATUS status; + + status = wb_seqnum_recv(subreq, &state->seqnum); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +static void winbindd_show_sequence_done_all(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_show_sequence_state *state = tevent_req_data( + req, struct winbindd_show_sequence_state); + NTSTATUS status; + + status = wb_seqnums_recv(subreq, state, &state->num_domains, + &state->domains, &state->statuses, + &state->seqnums); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_show_sequence_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_show_sequence_state *state = tevent_req_data( + req, struct winbindd_show_sequence_state); + NTSTATUS status; + char *extra_data; + int i; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + + if (state->one_domain) { + response->data.sequence_number = state->seqnum; + return NT_STATUS_OK; + } + + extra_data = talloc_strdup(response, ""); + if (extra_data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<state->num_domains; i++) { + if (!NT_STATUS_IS_OK(state->statuses[i]) + || (state->seqnums[i] == DOM_SEQUENCE_NONE)) { + extra_data = talloc_asprintf_append_buffer( + extra_data, "%s : DISCONNECTED\n", + state->domains[i]->name); + } else { + extra_data = talloc_asprintf_append_buffer( + extra_data, "%s : %d\n", + state->domains[i]->name, + (int)state->seqnums[i]); + } + if (extra_data == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + response->extra_data.data = extra_data; + response->length += talloc_get_size(extra_data); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_sids_to_xids.c b/source3/winbindd/winbindd_sids_to_xids.c new file mode 100644 index 0000000..187b299 --- /dev/null +++ b/source3/winbindd/winbindd_sids_to_xids.c @@ -0,0 +1,164 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_SIDS_TO_XIDS + Copyright (C) Volker Lendecke 2011 + Copyright (C) Michael Adam 2012 + + 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.h" +#include "../libcli/security/security.h" + + +struct winbindd_sids_to_xids_state { + struct tevent_context *ev; + struct dom_sid *sids; + uint32_t num_sids; + struct unixid *xids; +}; + +static void winbindd_sids_to_xids_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_sids_to_xids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_sids_to_xids_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_sids_to_xids_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + D_NOTICE("[%s (%u)] Winbind external command SIDS_TO_XIDS start.\n", + cli->client_name, + (unsigned int)cli->pid); + + if (request->extra_len == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (request->extra_data.data[request->extra_len-1] != '\0') { + D_DEBUG("Got invalid sids list\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + if (!parse_sidlist(state, request->extra_data.data, + &state->sids, &state->num_sids)) { + D_DEBUG("parse_sidlist failed\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + D_DEBUG("Resolving %"PRIu32" SID(s).\n", state->num_sids); + + subreq = wb_sids2xids_send(state, ev, state->sids, state->num_sids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, winbindd_sids_to_xids_done, req); + return req; +} + +static void winbindd_sids_to_xids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_sids_to_xids_state *state = tevent_req_data( + req, struct winbindd_sids_to_xids_state); + NTSTATUS status; + + state->xids = talloc_zero_array(state, struct unixid, state->num_sids); + if (tevent_req_nomem(state->xids, req)) { + return; + } + + status = wb_sids2xids_recv(subreq, state->xids, state->num_sids); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_sids_to_xids_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_sids_to_xids_state *state = tevent_req_data( + req, struct winbindd_sids_to_xids_state); + NTSTATUS status; + char *result = NULL; + uint32_t i; + + D_NOTICE("Winbind external command SIDS_TO_XIDS end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Could not convert sids: %s\n", nt_errstr(status)); + return status; + } + + result = talloc_strdup(response, ""); + + for (i=0; i<state->num_sids; i++) { + char type = '\0'; + bool found = true; + struct unixid xid; + + xid = state->xids[i]; + + switch (xid.type) { + case ID_TYPE_UID: + type = 'U'; + break; + case ID_TYPE_GID: + type = 'G'; + break; + case ID_TYPE_BOTH: + type = 'B'; + break; + default: + found = false; + break; + } + + if (xid.id == UINT32_MAX) { + found = false; + } + + if (found) { + talloc_asprintf_addbuf( + &result, + "%c%lu\n", + type, + (unsigned long)xid.id); + } else { + talloc_asprintf_addbuf(&result, "\n"); + } + } + + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + + response->extra_data.data = result; + response->length += talloc_get_size(result); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_traceid.c b/source3/winbindd/winbindd_traceid.c new file mode 100644 index 0000000..acf16be --- /dev/null +++ b/source3/winbindd/winbindd_traceid.c @@ -0,0 +1,147 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 Red Hat + + 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 "lib/util/debug.h" +#include "winbindd_traceid.h" +#include "tevent.h" + +static void debug_traceid_trace_fde(struct tevent_fd *fde, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current traceid id when the event is created. */ + tevent_fd_set_tag(fde, debug_traceid_get()); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the traceid id when a handler is being called. */ + debug_traceid_set(tevent_fd_get_tag(fde)); + break; + default: + /* Do nothing. */ + break; + } +} + +static void debug_traceid_trace_signal(struct tevent_signal *se, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current traceid id when the event is created. */ + tevent_signal_set_tag(se, debug_traceid_get()); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the traceid id when a handler is being called. */ + debug_traceid_set(tevent_signal_get_tag(se)); + break; + default: + /* Do nothing. */ + break; + } +} + +static void debug_traceid_trace_timer(struct tevent_timer *timer, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current traceid id when the event is created. */ + tevent_timer_set_tag(timer, debug_traceid_get()); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the traceid id when a handler is being called. */ + debug_traceid_set(tevent_timer_get_tag(timer)); + break; + default: + /* Do nothing. */ + break; + } +} + +static void debug_traceid_trace_immediate(struct tevent_immediate *im, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current traceid id when the event is created. */ + tevent_immediate_set_tag(im, debug_traceid_get()); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the traceid id when a handler is being called. */ + debug_traceid_set(tevent_immediate_get_tag(im)); + break; + default: + /* Do nothing. */ + break; + } +} + +static void debug_traceid_trace_queue(struct tevent_queue_entry *qe, + enum tevent_event_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_EVENT_TRACE_ATTACH: + /* Assign the current traceid id when the event is created. */ + tevent_queue_entry_set_tag(qe, debug_traceid_get()); + break; + case TEVENT_EVENT_TRACE_BEFORE_HANDLER: + /* Set the traceid id when a handler is being called. */ + debug_traceid_set(tevent_queue_entry_get_tag(qe)); + break; + default: + /* Do nothing. */ + break; + } +} + +static void debug_traceid_trace_loop(enum tevent_trace_point point, + void *private_data) +{ + switch (point) { + case TEVENT_TRACE_AFTER_LOOP_ONCE: + /* Reset traceid id when we got back to the loop. An event handler + * that set traceid id was fired. This tracepoint represents a place + * after the event handler was finished, we need to restore traceid + * id to 1 (out of request). 0 means not initialized. + */ + debug_traceid_set(1); + break; + default: + /* Do nothing. */ + break; + } +} + +void winbind_debug_traceid_setup(struct tevent_context *ev) +{ + tevent_set_trace_callback(ev, debug_traceid_trace_loop, NULL); + tevent_set_trace_fd_callback(ev, debug_traceid_trace_fde, NULL); + tevent_set_trace_signal_callback(ev, debug_traceid_trace_signal, NULL); + tevent_set_trace_timer_callback(ev, debug_traceid_trace_timer, NULL); + tevent_set_trace_immediate_callback(ev, debug_traceid_trace_immediate, NULL); + tevent_set_trace_queue_callback(ev, debug_traceid_trace_queue, NULL); + debug_traceid_set(1); +} diff --git a/source3/winbindd/winbindd_traceid.h b/source3/winbindd/winbindd_traceid.h new file mode 100644 index 0000000..d06e3ef --- /dev/null +++ b/source3/winbindd/winbindd_traceid.h @@ -0,0 +1,29 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2021 Red Hat + + 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 _DEBUG_TRACE_ID_ +#define _DEBUG_TRACE_ID_ + +#include <tevent.h> + +/* Setup traceid tracking on tevent context. */ +void winbind_debug_traceid_setup(struct tevent_context *ev); + +#endif /* _DEBUG_TRACE_ID_ */ diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c new file mode 100644 index 0000000..7527a78 --- /dev/null +++ b/source3/winbindd/winbindd_util.c @@ -0,0 +1,2243 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon for ntdom nss module + + Copyright (C) Tim Potter 2000-2001 + Copyright (C) 2001 by Martin Pool <mbp@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.h" +#include "lib/util_unixsids.h" +#include "secrets.h" +#include "../libcli/security/security.h" +#include "../libcli/auth/pam_errors.h" +#include "passdb/machine_sid.h" +#include "passdb.h" +#include "source4/lib/messaging/messaging.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "auth/credentials/credentials.h" +#include "libsmb/samlogon_cache.h" +#include "lib/util/smb_strtox.h" +#include "lib/util/string_wrappers.h" +#include "lib/global_contexts.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +/** + * @file winbindd_util.c + * + * Winbind daemon for NT domain authentication nss module. + **/ + +/* The list of trusted domains. Note that the list can be deleted and + recreated using the init_domain_list() function so pointers to + individual winbindd_domain structures cannot be made. Keep a copy of + the domain name instead. */ + +static struct winbindd_domain *_domain_list = NULL; + +struct winbindd_domain *domain_list(void) +{ + /* Initialise list */ + + if ((!_domain_list) && (!init_domain_list())) { + smb_panic("Init_domain_list failed"); + } + + return _domain_list; +} + +/* Free all entries in the trusted domain list */ + +static void free_domain_list(void) +{ + struct winbindd_domain *domain = _domain_list; + + while(domain) { + struct winbindd_domain *next = domain->next; + + DLIST_REMOVE(_domain_list, domain); + TALLOC_FREE(domain); + domain = next; + } +} + +/** + * Iterator for winbindd's domain list. + * To be used (e.g.) in tevent based loops. + */ +struct winbindd_domain *wb_next_domain(struct winbindd_domain *domain) +{ + if (domain == NULL) { + domain = domain_list(); + } else { + domain = domain->next; + } + + if ((domain != NULL) && + (lp_server_role() != ROLE_ACTIVE_DIRECTORY_DC) && + sid_check_is_our_sam(&domain->sid)) + { + domain = domain->next; + } + + return domain; +} + +static bool is_internal_domain(const struct dom_sid *sid) +{ + if (sid == NULL) + return False; + + return (sid_check_is_our_sam(sid) || sid_check_is_builtin(sid)); +} + +/* Add a trusted domain to our list of domains. + If the domain already exists in the list, + return it and don't re-initialize. */ + +static NTSTATUS add_trusted_domain(const char *domain_name, + const char *dns_name, + const struct dom_sid *sid, + uint32_t trust_type, + uint32_t trust_flags, + uint32_t trust_attribs, + enum netr_SchannelType secure_channel_type, + struct winbindd_domain *routing_domain, + struct winbindd_domain **_d) +{ + struct winbindd_domain *domain = NULL; + int role = lp_server_role(); + struct dom_sid_buf buf; + + if (is_null_sid(sid)) { + DBG_ERR("Got null SID for domain [%s]\n", domain_name); + return NT_STATUS_INVALID_PARAMETER; + } + + if (secure_channel_type == SEC_CHAN_NULL && !is_allowed_domain(domain_name)) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + /* + * We can't call domain_list() as this function is called from + * init_domain_list() and we'll get stuck in a loop. + */ + for (domain = _domain_list; domain; domain = domain->next) { + if (strequal(domain_name, domain->name)) { + break; + } + } + + if (domain != NULL) { + struct winbindd_domain *check_domain = NULL; + + for (check_domain = _domain_list; + check_domain != NULL; + check_domain = check_domain->next) + { + if (check_domain == domain) { + continue; + } + + if (dom_sid_equal(&check_domain->sid, sid)) { + break; + } + } + + if (check_domain != NULL) { + DBG_ERR("SID [%s] already used by domain [%s], " + "expected [%s]\n", + dom_sid_str_buf(sid, &buf), + check_domain->name, + domain->name); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if ((domain != NULL) && (dns_name != NULL)) { + struct winbindd_domain *check_domain = NULL; + + for (check_domain = _domain_list; + check_domain != NULL; + check_domain = check_domain->next) + { + if (check_domain == domain) { + continue; + } + + if (strequal(check_domain->alt_name, dns_name)) { + break; + } + } + + if (check_domain != NULL) { + DBG_ERR("DNS name [%s] used by domain [%s], " + "expected [%s]\n", + dns_name, check_domain->name, + domain->name); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (domain != NULL) { + *_d = domain; + return NT_STATUS_OK; + } + + /* Create new domain entry */ + domain = talloc_zero(NULL, struct winbindd_domain); + if (domain == NULL) { + return NT_STATUS_NO_MEMORY; + } + + domain->children = talloc_zero_array(domain, + struct winbindd_child, + lp_winbind_max_domain_connections()); + if (domain->children == NULL) { + TALLOC_FREE(domain); + return NT_STATUS_NO_MEMORY; + } + + domain->queue = tevent_queue_create(domain, "winbind_domain"); + if (domain->queue == NULL) { + TALLOC_FREE(domain); + return NT_STATUS_NO_MEMORY; + } + + domain->binding_handle = wbint_binding_handle(domain, domain, NULL); + if (domain->binding_handle == NULL) { + TALLOC_FREE(domain); + return NT_STATUS_NO_MEMORY; + } + + domain->name = talloc_strdup(domain, domain_name); + if (domain->name == NULL) { + TALLOC_FREE(domain); + return NT_STATUS_NO_MEMORY; + } + + if (dns_name != NULL) { + domain->alt_name = talloc_strdup(domain, dns_name); + if (domain->alt_name == NULL) { + TALLOC_FREE(domain); + return NT_STATUS_NO_MEMORY; + } + } + + domain->backend = NULL; + domain->internal = is_internal_domain(sid); + domain->secure_channel_type = secure_channel_type; + domain->sequence_number = DOM_SEQUENCE_NONE; + domain->last_seq_check = 0; + domain->initialized = false; + domain->online = is_internal_domain(sid); + domain->domain_flags = trust_flags; + domain->domain_type = trust_type; + domain->domain_trust_attribs = trust_attribs; + domain->secure_channel_type = secure_channel_type; + domain->routing_domain = routing_domain; + sid_copy(&domain->sid, sid); + + /* Is this our primary domain ? */ + if (role == ROLE_DOMAIN_MEMBER) { + domain->primary = strequal(domain_name, lp_workgroup()); + } else { + domain->primary = strequal(domain_name, get_global_sam_name()); + } + + if (domain->primary) { + if (role == ROLE_ACTIVE_DIRECTORY_DC) { + domain->active_directory = true; + } + if (lp_security() == SEC_ADS) { + domain->active_directory = true; + } + } else if (!domain->internal) { + if (domain->domain_type == LSA_TRUST_TYPE_UPLEVEL) { + domain->active_directory = true; + } + } + + domain->can_do_ncacn_ip_tcp = domain->active_directory; + + /* Link to domain list */ + DLIST_ADD_END(_domain_list, domain); + + wcache_tdc_add_domain( domain ); + + setup_domain_child(domain); + + DBG_NOTICE("Added domain [%s] [%s] [%s]\n", + domain->name, domain->alt_name, + dom_sid_str_buf(&domain->sid, &buf)); + + *_d = domain; + return NT_STATUS_OK; +} + +bool set_routing_domain(struct winbindd_domain *domain, + struct winbindd_domain *routing_domain) +{ + if (domain->routing_domain == NULL) { + domain->routing_domain = routing_domain; + return true; + } + if (domain->routing_domain != routing_domain) { + return false; + } + return true; +} + +bool add_trusted_domain_from_auth(uint16_t validation_level, + struct info3_text *info3, + struct info6_text *info6) +{ + struct winbindd_domain *domain = NULL; + struct dom_sid domain_sid; + const char *dns_domainname = NULL; + NTSTATUS status; + bool ok; + + /* + * We got a successful auth from a domain that might not yet be in our + * domain list. If we're a member we trust our DC who authenticated the + * user from that domain and add the domain to our list on-the-fly. If + * we're a DC we rely on configured trusts and don't add on-the-fly. + */ + + if (IS_DC) { + return true; + } + + ok = dom_sid_parse(info3->dom_sid, &domain_sid); + if (!ok) { + DBG_NOTICE("dom_sid_parse [%s] failed\n", info3->dom_sid); + return false; + } + + if (validation_level == 6) { + if (!strequal(info6->dns_domainname, "")) { + dns_domainname = info6->dns_domainname; + } + } + + status = add_trusted_domain(info3->logon_dom, + dns_domainname, + &domain_sid, + 0, + NETR_TRUST_FLAG_OUTBOUND, + 0, + SEC_CHAN_NULL, + find_default_route_domain(), + &domain); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) + { + DBG_DEBUG("Adding domain [%s] with sid [%s] failed\n", + info3->logon_dom, info3->dom_sid); + return false; + } + + return true; +} + +bool domain_is_forest_root(const struct winbindd_domain *domain) +{ + const uint32_t fr_flags = + (NETR_TRUST_FLAG_TREEROOT|NETR_TRUST_FLAG_IN_FOREST); + + return ((domain->domain_flags & fr_flags) == fr_flags); +} + +/******************************************************************** + rescan our domains looking for new trusted domains +********************************************************************/ + +struct trustdom_state { + struct winbindd_domain *domain; + struct netr_DomainTrustList trusts; +}; + +static void trustdom_list_done(struct tevent_req *req); +static void rescan_forest_root_trusts( void ); +static void rescan_forest_trusts( void ); + +static void add_trusted_domains( struct winbindd_domain *domain ) +{ + struct tevent_context *ev = global_event_context(); + struct trustdom_state *state; + struct tevent_req *req; + const char *client_name = NULL; + pid_t client_pid; + + state = talloc_zero(NULL, struct trustdom_state); + if (state == NULL) { + DEBUG(0, ("talloc failed\n")); + return; + } + state->domain = domain; + + /* Called from timer, not from a real client */ + client_name = getprogname(); + client_pid = getpid(); + + req = dcerpc_wbint_ListTrustedDomains_send(state, + ev, + dom_child_handle(domain), + client_name, + client_pid, + &state->trusts); + if (req == NULL) { + DBG_ERR("dcerpc_wbint_ListTrustedDomains_send failed\n"); + TALLOC_FREE(state); + return; + } + tevent_req_set_callback(req, trustdom_list_done, state); +} + +static void trustdom_list_done(struct tevent_req *req) +{ + struct trustdom_state *state = tevent_req_callback_data( + req, struct trustdom_state); + bool within_forest = false; + NTSTATUS status, result; + uint32_t i; + + /* + * Only when we enumerate our primary domain + * or our forest root domain, we should keep + * the NETR_TRUST_FLAG_IN_FOREST flag, in + * all other cases we need to clear it as the domain + * is not part of our forest. + */ + if (state->domain->primary) { + within_forest = true; + } else if (domain_is_forest_root(state->domain)) { + within_forest = true; + } + + status = dcerpc_wbint_ListTrustedDomains_recv(req, state, &result); + if (any_nt_status_not_ok(status, result, &status)) { + DBG_WARNING("Could not receive trusts for domain %s: %s-%s\n", + state->domain->name, nt_errstr(status), + nt_errstr(result)); + TALLOC_FREE(state); + return; + } + + for (i=0; i<state->trusts.count; i++) { + struct netr_DomainTrust *trust = &state->trusts.array[i]; + struct winbindd_domain *domain = NULL; + + if (!within_forest) { + trust->trust_flags &= ~NETR_TRUST_FLAG_IN_FOREST; + } + + if (!state->domain->primary) { + trust->trust_flags &= ~NETR_TRUST_FLAG_PRIMARY; + } + + /* + * We always call add_trusted_domain() cause on an existing + * domain structure, it will update the SID if necessary. + * This is important because we need the SID for sibling + * domains. + */ + status = add_trusted_domain(trust->netbios_name, + trust->dns_name, + trust->sid, + trust->trust_type, + trust->trust_flags, + trust->trust_attributes, + SEC_CHAN_NULL, + find_default_route_domain(), + &domain); + if (!NT_STATUS_IS_OK(status) && + !NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) + { + DBG_NOTICE("add_trusted_domain returned %s\n", + nt_errstr(status)); + return; + } + } + + /* + Cases to consider when scanning trusts: + (a) we are calling from a child domain (primary && !forest_root) + (b) we are calling from the root of the forest (primary && forest_root) + (c) we are calling from a trusted forest domain (!primary + && !forest_root) + */ + + if (state->domain->primary) { + /* If this is our primary domain and we are not in the + forest root, we have to scan the root trusts first */ + + if (!domain_is_forest_root(state->domain)) + rescan_forest_root_trusts(); + else + rescan_forest_trusts(); + + } else if (domain_is_forest_root(state->domain)) { + /* Once we have done root forest trust search, we can + go on to search the trusted forests */ + + rescan_forest_trusts(); + } + + TALLOC_FREE(state); + + return; +} + +/******************************************************************** + Scan the trusts of our forest root +********************************************************************/ + +static void rescan_forest_root_trusts( void ) +{ + struct winbindd_tdc_domain *dom_list = NULL; + size_t num_trusts = 0; + size_t i; + NTSTATUS status; + + /* The only transitive trusts supported by Windows 2003 AD are + (a) Parent-Child, (b) Tree-Root, and (c) Forest. The + first two are handled in forest and listed by + DsEnumerateDomainTrusts(). Forest trusts are not so we + have to do that ourselves. */ + + if ( !wcache_tdc_fetch_list( &dom_list, &num_trusts ) ) + return; + + for ( i=0; i<num_trusts; i++ ) { + struct winbindd_domain *d = NULL; + + /* Find the forest root. Don't necessarily trust + the domain_list() as our primary domain may not + have been initialized. */ + + if ( !(dom_list[i].trust_flags & NETR_TRUST_FLAG_TREEROOT) ) { + continue; + } + + /* Here's the forest root */ + + d = find_domain_from_name_noinit( dom_list[i].domain_name ); + if (d == NULL) { + status = add_trusted_domain(dom_list[i].domain_name, + dom_list[i].dns_name, + &dom_list[i].sid, + dom_list[i].trust_type, + dom_list[i].trust_flags, + dom_list[i].trust_attribs, + SEC_CHAN_NULL, + find_default_route_domain(), + &d); + + if (!NT_STATUS_IS_OK(status) && + NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) + { + DBG_ERR("add_trusted_domain returned %s\n", + nt_errstr(status)); + return; + } + } + if (d == NULL) { + continue; + } + + DEBUG(10,("rescan_forest_root_trusts: Following trust path " + "for domain tree root %s (%s)\n", + d->name, d->alt_name )); + + d->domain_flags = dom_list[i].trust_flags; + d->domain_type = dom_list[i].trust_type; + d->domain_trust_attribs = dom_list[i].trust_attribs; + + add_trusted_domains( d ); + + break; + } + + TALLOC_FREE( dom_list ); + + return; +} + +/******************************************************************** + scan the transitive forest trusts (not our own) +********************************************************************/ + + +static void rescan_forest_trusts( void ) +{ + struct winbindd_domain *d = NULL; + struct winbindd_tdc_domain *dom_list = NULL; + size_t num_trusts = 0; + size_t i; + NTSTATUS status; + + /* The only transitive trusts supported by Windows 2003 AD are + (a) Parent-Child, (b) Tree-Root, and (c) Forest. The + first two are handled in forest and listed by + DsEnumerateDomainTrusts(). Forest trusts are not so we + have to do that ourselves. */ + + if ( !wcache_tdc_fetch_list( &dom_list, &num_trusts ) ) + return; + + for ( i=0; i<num_trusts; i++ ) { + uint32_t flags = dom_list[i].trust_flags; + uint32_t type = dom_list[i].trust_type; + uint32_t attribs = dom_list[i].trust_attribs; + + d = find_domain_from_name_noinit( dom_list[i].domain_name ); + + /* ignore our primary and internal domains */ + + if ( d && (d->internal || d->primary ) ) + continue; + + if ( (flags & NETR_TRUST_FLAG_INBOUND) && + (type == LSA_TRUST_TYPE_UPLEVEL) && + (attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) ) + { + /* add the trusted domain if we don't know + about it */ + + if (d == NULL) { + status = add_trusted_domain( + dom_list[i].domain_name, + dom_list[i].dns_name, + &dom_list[i].sid, + type, + flags, + attribs, + SEC_CHAN_NULL, + find_default_route_domain(), + &d); + if (!NT_STATUS_IS_OK(status) && + NT_STATUS_EQUAL(status, + NT_STATUS_NO_SUCH_DOMAIN)) + { + DBG_ERR("add_trusted_domain: %s\n", + nt_errstr(status)); + return; + } + } + + if (d == NULL) { + continue; + } + + DEBUG(10,("Following trust path for domain %s (%s)\n", + d->name, d->alt_name )); + add_trusted_domains( d ); + } + } + + TALLOC_FREE( dom_list ); + + return; +} + +/********************************************************************* + The process of updating the trusted domain list is a three step + async process: + (a) ask our domain + (b) ask the root domain in our forest + (c) ask a DC in any Win2003 trusted forests +*********************************************************************/ + +void rescan_trusted_domains(struct tevent_context *ev, struct tevent_timer *te, + struct timeval now, void *private_data) +{ + TALLOC_FREE(te); + + /* I used to clear the cache here and start over but that + caused problems in child processes that needed the + trust dom list early on. Removing it means we + could have some trusted domains listed that have been + removed from our primary domain's DC until a full + restart. This should be ok since I think this is what + Windows does as well. */ + + /* this will only add new domains we didn't already know about + in the domain_list()*/ + + add_trusted_domains( find_our_domain() ); + + te = tevent_add_timer( + ev, NULL, timeval_current_ofs(WINBINDD_RESCAN_FREQ, 0), + rescan_trusted_domains, NULL); + /* + * If te == NULL, there's not much we can do here. Don't fail, the + * only thing we miss is new trusted domains. + */ + + return; +} + +static void wbd_ping_dc_done(struct tevent_req *subreq); + +void winbindd_ping_offline_domains(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval now, + void *private_data) +{ + struct winbindd_domain *domain = NULL; + + TALLOC_FREE(te); + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + DBG_DEBUG("Domain %s is %s\n", + domain->name, + domain->online ? "online" : "offline"); + + if (get_global_winbindd_state_offline()) { + DBG_DEBUG("We are globally offline, do nothing.\n"); + break; + } + + if (domain->online || + domain->check_online_event != NULL || + domain->secure_channel_type == SEC_CHAN_NULL) { + continue; + } + + winbindd_flush_negative_conn_cache(domain); + + domain->check_online_event = + dcerpc_wbint_PingDc_send(domain, + ev, + dom_child_handle(domain), + &domain->ping_dcname); + if (domain->check_online_event == NULL) { + DBG_WARNING("Failed to schedule ping, no-memory\n"); + continue; + } + + tevent_req_set_callback(domain->check_online_event, + wbd_ping_dc_done, domain); + } + + te = tevent_add_timer(ev, + NULL, + timeval_current_ofs(lp_winbind_reconnect_delay(), + 0), + winbindd_ping_offline_domains, + NULL); + if (te == NULL) { + DBG_ERR("Failed to schedule winbindd_ping_offline_domains()\n"); + } + + return; +} + +static void wbd_ping_dc_done(struct tevent_req *subreq) +{ + struct winbindd_domain *domain = + tevent_req_callback_data(subreq, + struct winbindd_domain); + NTSTATUS status, result; + + SMB_ASSERT(subreq == domain->check_online_event); + domain->check_online_event = NULL; + + status = dcerpc_wbint_PingDc_recv(subreq, domain, &result); + TALLOC_FREE(subreq); + if (any_nt_status_not_ok(status, result, &status)) { + DBG_WARNING("dcerpc_wbint_PingDc_recv failed for domain: " + "%s - %s\n", + domain->name, + nt_errstr(status)); + return; + } + + DBG_DEBUG("dcerpc_wbint_PingDc_recv() succeeded, " + "domain: %s, dc-name: %s\n", + domain->name, + domain->ping_dcname); + + talloc_free(discard_const(domain->ping_dcname)); + domain->ping_dcname = NULL; + + return; +} + +static void wb_imsg_new_trusted_domain(struct imessaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + size_t num_fds, + int *fds, + DATA_BLOB *data) +{ + bool ok; + + if (num_fds != 0) { + DBG_WARNING("Received %zu fds, ignoring message\n", num_fds); + return; + } + + DBG_NOTICE("Rescanning trusted domains\n"); + + ok = add_trusted_domains_dc(); + if (!ok) { + DBG_ERR("Failed to reload trusted domains\n"); + } +} + +/* + * We did not get the secret when we queried secrets.tdb, so read it + * from secrets.tdb and re-sync the databases + */ +static bool migrate_secrets_tdb_to_ldb(struct winbindd_domain *domain) +{ + bool ok; + struct cli_credentials *creds; + NTSTATUS can_migrate = pdb_get_trust_credentials(domain->name, + NULL, domain, &creds); + if (!NT_STATUS_IS_OK(can_migrate)) { + DEBUG(0, ("Failed to fetch our own local AD domain join " + "password for winbindd's internal use, both from " + "secrets.tdb and secrets.ldb: %s\n", + nt_errstr(can_migrate))); + return false; + } + + /* + * NOTE: It is very unlikely we end up here if there is an + * oldpass, because a new password is created at + * classicupgrade, so this is not a concern. + */ + ok = secrets_store_machine_pw_sync(cli_credentials_get_password(creds), + NULL /* oldpass */, + cli_credentials_get_domain(creds), + cli_credentials_get_realm(creds), + cli_credentials_get_salt_principal(creds), + 0, /* Supported enc types, unused */ + &domain->sid, + cli_credentials_get_password_last_changed_time(creds), + cli_credentials_get_secure_channel_type(creds), + false /* do_delete: Do not delete */); + TALLOC_FREE(creds); + if (ok == false) { + DEBUG(0, ("Failed to write our own " + "local AD domain join password for " + "winbindd's internal use into secrets.tdb\n")); + return false; + } + return true; +} + +bool add_trusted_domains_dc(void) +{ + struct winbindd_domain *domain = NULL; + struct pdb_trusted_domain **domains = NULL; + uint32_t num_domains = 0; + uint32_t i; + NTSTATUS status; + + if (!(pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX)) { + struct trustdom_info **ti = NULL; + + status = pdb_enum_trusteddoms(talloc_tos(), &num_domains, &ti); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("pdb_enum_trusteddoms() failed - %s\n", + nt_errstr(status)); + return false; + } + + for (i = 0; i < num_domains; i++) { + status = add_trusted_domain(ti[i]->name, + NULL, + &ti[i]->sid, + LSA_TRUST_TYPE_DOWNLEVEL, + NETR_TRUST_FLAG_OUTBOUND, + 0, + SEC_CHAN_DOMAIN, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("add_trusted_domain returned %s\n", + nt_errstr(status)); + return false; + } + } + + return true; + } + + status = pdb_enum_trusted_domains(talloc_tos(), &num_domains, &domains); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("pdb_enum_trusted_domains() failed - %s\n", + nt_errstr(status)); + return false; + } + + for (i = 0; i < num_domains; i++) { + enum netr_SchannelType sec_chan_type = SEC_CHAN_DOMAIN; + uint32_t trust_flags = 0; + + if (domains[i]->trust_type == LSA_TRUST_TYPE_UPLEVEL) { + sec_chan_type = SEC_CHAN_DNS_DOMAIN; + } + + if (!(domains[i]->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND)) { + sec_chan_type = SEC_CHAN_NULL; + } + + if (domains[i]->trust_direction & LSA_TRUST_DIRECTION_INBOUND) { + trust_flags |= NETR_TRUST_FLAG_INBOUND; + } + if (domains[i]->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND) { + trust_flags |= NETR_TRUST_FLAG_OUTBOUND; + } + if (domains[i]->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + trust_flags |= NETR_TRUST_FLAG_IN_FOREST; + } + + if (domains[i]->trust_attributes & LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION) { + /* + * We don't support selective authentication yet. + */ + DBG_WARNING("Ignoring CROSS_ORGANIZATION trust to " + "domain[%s/%s]\n", + domains[i]->netbios_name, + domains[i]->domain_name); + continue; + } + + status = add_trusted_domain(domains[i]->netbios_name, + domains[i]->domain_name, + &domains[i]->security_identifier, + domains[i]->trust_type, + trust_flags, + domains[i]->trust_attributes, + sec_chan_type, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("add_trusted_domain returned %s\n", + nt_errstr(status)); + return false; + } + + if (domains[i]->trust_type == LSA_TRUST_TYPE_UPLEVEL) { + domain->active_directory = true; + } + domain->domain_type = domains[i]->trust_type; + domain->domain_trust_attribs = domains[i]->trust_attributes; + } + + for (i = 0; i < num_domains; i++) { + struct ForestTrustInfo fti; + uint32_t fi; + enum ndr_err_code ndr_err; + struct winbindd_domain *routing_domain = NULL; + + if (domains[i]->trust_type != LSA_TRUST_TYPE_UPLEVEL) { + continue; + } + + if (!(domains[i]->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) { + continue; + } + + if (domains[i]->trust_forest_trust_info.length == 0) { + continue; + } + + routing_domain = find_domain_from_name_noinit( + domains[i]->netbios_name); + if (routing_domain == NULL) { + DBG_ERR("Can't find winbindd domain [%s]\n", + domains[i]->netbios_name); + return false; + } + + ndr_err = ndr_pull_struct_blob_all( + &domains[i]->trust_forest_trust_info, + talloc_tos(), &fti, + (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("ndr_pull_ForestTrustInfo(%s) - %s\n", + domains[i]->netbios_name, + ndr_map_error2string(ndr_err)); + return false; + } + + for (fi = 0; fi < fti.count; fi++) { + struct ForestTrustInfoRecord *rec = + &fti.records[fi].record; + struct ForestTrustDataDomainInfo *drec = NULL; + + if (rec->type != FOREST_TRUST_DOMAIN_INFO) { + continue; + } + drec = &rec->data.info; + + if (rec->flags & LSA_NB_DISABLED_MASK) { + continue; + } + + if (rec->flags & LSA_SID_DISABLED_MASK) { + continue; + } + + /* + * TODO: + * also try to find a matching + * LSA_TLN_DISABLED_MASK ??? + */ + + domain = find_domain_from_name_noinit(drec->netbios_name.string); + if (domain != NULL) { + continue; + } + + status = add_trusted_domain(drec->netbios_name.string, + drec->dns_name.string, + &drec->sid, + LSA_TRUST_TYPE_UPLEVEL, + NETR_TRUST_FLAG_OUTBOUND, + 0, + SEC_CHAN_NULL, + routing_domain, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("add_trusted_domain returned %s\n", + nt_errstr(status)); + return false; + } + if (domain == NULL) { + continue; + } + } + } + + return true; +} + + +/* Look up global info for the winbind daemon */ +bool init_domain_list(void) +{ + int role = lp_server_role(); + struct pdb_domain_info *pdb_domain_info = NULL; + struct winbindd_domain *domain = NULL; + NTSTATUS status; + bool ok; + + /* Free existing list */ + free_domain_list(); + + /* BUILTIN domain */ + + status = add_trusted_domain("BUILTIN", + NULL, + &global_sid_Builtin, + LSA_TRUST_TYPE_DOWNLEVEL, + 0, /* trust_flags */ + 0, /* trust_attribs */ + SEC_CHAN_LOCAL, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("add_trusted_domain BUILTIN returned %s\n", + nt_errstr(status)); + return false; + } + + /* Local SAM */ + + /* + * In case the passdb backend is passdb_dsdb the domain SID comes from + * dsdb, not from secrets.tdb. As we use the domain SID in various + * places, we must ensure the domain SID is migrated from dsdb to + * secrets.tdb before get_global_sam_sid() is called the first time. + * + * The migration is done as part of the passdb_dsdb initialisation, + * calling pdb_get_domain_info() triggers it. + */ + pdb_domain_info = pdb_get_domain_info(talloc_tos()); + + if ( role == ROLE_ACTIVE_DIRECTORY_DC ) { + uint32_t trust_flags; + bool is_root; + enum netr_SchannelType sec_chan_type; + const char *account_name; + struct samr_Password current_nt_hash; + + if (pdb_domain_info == NULL) { + DEBUG(0, ("Failed to fetch our own local AD " + "domain info from sam.ldb\n")); + return false; + } + + trust_flags = NETR_TRUST_FLAG_PRIMARY; + trust_flags |= NETR_TRUST_FLAG_IN_FOREST; + trust_flags |= NETR_TRUST_FLAG_NATIVE; + trust_flags |= NETR_TRUST_FLAG_OUTBOUND; + + is_root = strequal(pdb_domain_info->dns_domain, + pdb_domain_info->dns_forest); + if (is_root) { + trust_flags |= NETR_TRUST_FLAG_TREEROOT; + } + + status = add_trusted_domain(pdb_domain_info->name, + pdb_domain_info->dns_domain, + &pdb_domain_info->sid, + LSA_TRUST_TYPE_UPLEVEL, + trust_flags, + LSA_TRUST_ATTRIBUTE_WITHIN_FOREST, + SEC_CHAN_BDC, + NULL, + &domain); + TALLOC_FREE(pdb_domain_info); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to add our own local AD " + "domain to winbindd's internal list\n"); + return false; + } + + /* + * We need to call this to find out if we are an RODC + */ + ok = get_trust_pw_hash(domain->name, + current_nt_hash.hash, + &account_name, + &sec_chan_type); + if (!ok) { + /* + * If get_trust_pw_hash() fails, then try and + * fetch the password from the more recent of + * secrets.{ldb,tdb} using the + * pdb_get_trust_credentials() + */ + ok = migrate_secrets_tdb_to_ldb(domain); + + if (!ok) { + DEBUG(0, ("Failed to migrate our own " + "local AD domain join password for " + "winbindd's internal use into " + "secrets.tdb\n")); + return false; + } + ok = get_trust_pw_hash(domain->name, + current_nt_hash.hash, + &account_name, + &sec_chan_type); + if (!ok) { + DEBUG(0, ("Failed to find our own just " + "written local AD domain join " + "password for winbindd's internal " + "use in secrets.tdb\n")); + return false; + } + } + + domain->secure_channel_type = sec_chan_type; + if (sec_chan_type == SEC_CHAN_RODC) { + domain->rodc = true; + } + + } else { + uint32_t trust_flags; + enum netr_SchannelType secure_channel_type; + + trust_flags = NETR_TRUST_FLAG_OUTBOUND; + if (role != ROLE_DOMAIN_MEMBER) { + trust_flags |= NETR_TRUST_FLAG_PRIMARY; + } + + if (role > ROLE_DOMAIN_MEMBER) { + secure_channel_type = SEC_CHAN_BDC; + } else { + secure_channel_type = SEC_CHAN_LOCAL; + } + + if ((pdb_domain_info != NULL) && (role == ROLE_IPA_DC)) { + /* This is IPA DC that presents itself as + * an Active Directory domain controller to trusted AD + * forests but in fact is a classic domain controller. + */ + trust_flags = NETR_TRUST_FLAG_PRIMARY; + trust_flags |= NETR_TRUST_FLAG_IN_FOREST; + trust_flags |= NETR_TRUST_FLAG_NATIVE; + trust_flags |= NETR_TRUST_FLAG_OUTBOUND; + trust_flags |= NETR_TRUST_FLAG_TREEROOT; + status = add_trusted_domain(pdb_domain_info->name, + pdb_domain_info->dns_domain, + &pdb_domain_info->sid, + LSA_TRUST_TYPE_UPLEVEL, + trust_flags, + LSA_TRUST_ATTRIBUTE_WITHIN_FOREST, + secure_channel_type, + NULL, + &domain); + TALLOC_FREE(pdb_domain_info); + } else { + status = add_trusted_domain(get_global_sam_name(), + NULL, + get_global_sam_sid(), + LSA_TRUST_TYPE_DOWNLEVEL, + trust_flags, + 0, /* trust_attribs */ + secure_channel_type, + NULL, + &domain); + } + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to add local SAM to " + "domain to winbindd's internal list\n"); + return false; + } + } + + if (IS_DC) { + ok = add_trusted_domains_dc(); + if (!ok) { + DBG_ERR("init_domain_list_dc failed\n"); + return false; + } + } + + if ( role == ROLE_DOMAIN_MEMBER ) { + struct dom_sid our_sid; + uint32_t trust_type; + + if (!secrets_fetch_domain_sid(lp_workgroup(), &our_sid)) { + DEBUG(0, ("Could not fetch our SID - did we join?\n")); + return False; + } + + if (lp_realm() != NULL) { + trust_type = LSA_TRUST_TYPE_UPLEVEL; + } else { + trust_type = LSA_TRUST_TYPE_DOWNLEVEL; + } + + status = add_trusted_domain(lp_workgroup(), + lp_realm(), + &our_sid, + trust_type, + NETR_TRUST_FLAG_PRIMARY| + NETR_TRUST_FLAG_OUTBOUND, + 0, /* trust_attribs */ + SEC_CHAN_WKSTA, + NULL, + &domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Failed to add local SAM to " + "domain to winbindd's internal list\n"); + return false; + } + } + + status = imessaging_register(winbind_imessaging_context(), NULL, + MSG_WINBIND_RELOAD_TRUSTED_DOMAINS, + wb_imsg_new_trusted_domain); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("imessaging_register failed %s\n", nt_errstr(status)); + return false; + } + + return True; +} + +/** + * Given a domain name, return the struct winbindd domain info for it + * + * @note Do *not* pass lp_workgroup() to this function. domain_list + * may modify it's value, and free that pointer. Instead, our local + * domain may be found by calling find_our_domain(). + * directly. + * + * + * @return The domain structure for the named domain, if it is working. + */ + +struct winbindd_domain *find_domain_from_name_noinit(const char *domain_name) +{ + struct winbindd_domain *domain; + + /* Search through list */ + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + if (strequal(domain_name, domain->name)) { + return domain; + } + if (domain->alt_name == NULL) { + continue; + } + if (strequal(domain_name, domain->alt_name)) { + return domain; + } + } + + /* Not found */ + + return NULL; +} + +/** + * Given a domain name, return the struct winbindd domain if it's a direct + * outgoing trust + * + * @return The domain structure for the named domain, if it is a direct outgoing trust + */ +struct winbindd_domain *find_trust_from_name_noinit(const char *domain_name) +{ + struct winbindd_domain *domain = NULL; + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return NULL; +} + +struct winbindd_domain *find_domain_from_name(const char *domain_name) +{ + struct winbindd_domain *domain; + + domain = find_domain_from_name_noinit(domain_name); + + if (domain == NULL) + return NULL; + + if (!domain->initialized) + init_dc_connection(domain, false); + + return domain; +} + +/* Given a domain sid, return the struct winbindd domain info for it */ + +struct winbindd_domain *find_domain_from_sid_noinit(const struct dom_sid *sid) +{ + struct winbindd_domain *domain; + + /* Search through list */ + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + if (dom_sid_compare_domain(sid, &domain->sid) == 0) + return domain; + } + + /* Not found */ + + return NULL; +} + +/** + * Given a domain sid, return the struct winbindd domain if it's a direct + * outgoing trust + * + * @return The domain structure for the specified domain, if it is a direct outgoing trust + */ +struct winbindd_domain *find_trust_from_sid_noinit(const struct dom_sid *sid) +{ + struct winbindd_domain *domain = NULL; + + domain = find_domain_from_sid_noinit(sid); + if (domain == NULL) { + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return NULL; +} + +/* Given a domain sid, return the struct winbindd domain info for it */ + +struct winbindd_domain *find_domain_from_sid(const struct dom_sid *sid) +{ + struct winbindd_domain *domain; + + domain = find_domain_from_sid_noinit(sid); + + if (domain == NULL) + return NULL; + + if (!domain->initialized) + init_dc_connection(domain, false); + + return domain; +} + +struct winbindd_domain *find_our_domain(void) +{ + struct winbindd_domain *domain; + + /* Search through list */ + + for (domain = domain_list(); domain != NULL; domain = domain->next) { + if (domain->primary) + return domain; + } + + smb_panic("Could not find our domain"); + return NULL; +} + +struct winbindd_domain *find_default_route_domain(void) +{ + if (!IS_DC) { + return find_our_domain(); + } + DBG_DEBUG("Routing logic not yet implemented on a DC\n"); + return NULL; +} + +/* Find the appropriate domain to lookup a name or SID */ + +struct winbindd_domain *find_lookup_domain_from_sid(const struct dom_sid *sid) +{ + struct dom_sid_buf buf; + + DBG_DEBUG("SID [%s]\n", dom_sid_str_buf(sid, &buf)); + + /* + * SIDs in the S-1-22-{1,2} domain and well-known SIDs should be handled + * by our passdb. + */ + + if ( sid_check_is_in_unix_groups(sid) || + sid_check_is_unix_groups(sid) || + sid_check_is_in_unix_users(sid) || + sid_check_is_unix_users(sid) || + sid_check_is_our_sam(sid) || + sid_check_is_in_our_sam(sid) ) + { + return find_domain_from_sid(get_global_sam_sid()); + } + + if ( sid_check_is_builtin(sid) || + sid_check_is_in_builtin(sid) || + sid_check_is_wellknown_domain(sid, NULL) || + sid_check_is_in_wellknown_domain(sid) ) + { + return find_domain_from_sid(&global_sid_Builtin); + } + + if (IS_DC) { + struct winbindd_domain *domain = NULL; + + domain = find_domain_from_sid_noinit(sid); + if (domain == NULL) { + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return domain->routing_domain; + } + + /* On a member server a query for SID or name can always go to our + * primary DC. */ + + DEBUG(10, ("calling find_our_domain\n")); + return find_our_domain(); +} + +struct winbindd_domain *find_lookup_domain_from_name(const char *domain_name) +{ + bool predefined; + + if ( strequal(domain_name, unix_users_domain_name() ) || + strequal(domain_name, unix_groups_domain_name() ) ) + { + /* + * The "Unix User" and "Unix Group" domain are handled by + * passdb + */ + return find_domain_from_name_noinit( get_global_sam_name() ); + } + + if (strequal(domain_name, "BUILTIN") || + strequal(domain_name, get_global_sam_name())) { + return find_domain_from_name_noinit(domain_name); + } + + predefined = dom_sid_lookup_is_predefined_domain(domain_name); + if (predefined) { + return find_domain_from_name_noinit(builtin_domain_name()); + } + + if (IS_DC) { + struct winbindd_domain *domain = NULL; + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + return NULL; + } + + if (domain->secure_channel_type != SEC_CHAN_NULL) { + return domain; + } + + return domain->routing_domain; + } + + return find_our_domain(); +} + +/* Is this a domain which we may assume no DOMAIN\ prefix? */ + +static bool assume_domain(const char *domain) +{ + /* never assume the domain on a standalone server */ + + if ( lp_server_role() == ROLE_STANDALONE ) + return False; + + /* domain member servers may possibly assume for the domain name */ + + if ( lp_server_role() == ROLE_DOMAIN_MEMBER ) { + if ( !strequal(lp_workgroup(), domain) ) + return False; + + if ( lp_winbind_use_default_domain() ) + return True; + } + + /* only left with a domain controller */ + + if ( strequal(get_global_sam_name(), domain) ) { + return True; + } + + return False; +} + +/* Parse a DOMAIN\user or UPN string into a domain, namespace and a user */ +bool parse_domain_user(TALLOC_CTX *ctx, + const char *domuser, + char **pnamespace, + char **pdomain, + char **puser) +{ + char *p = NULL; + char *namespace = NULL; + char *domain = NULL; + char *user = NULL; + + if (strlen(domuser) == 0) { + return false; + } + + p = strchr(domuser, *lp_winbind_separator()); + if (p != NULL) { + user = talloc_strdup(ctx, p + 1); + if (user == NULL) { + goto fail; + } + domain = talloc_strdup(ctx, + domuser); + if (domain == NULL) { + goto fail; + } + domain[PTR_DIFF(p, domuser)] = '\0'; + namespace = talloc_strdup(ctx, domain); + if (namespace == NULL) { + goto fail; + } + } else { + user = talloc_strdup(ctx, domuser); + if (user == NULL) { + goto fail; + } + p = strchr(domuser, '@'); + if (p != NULL) { + /* upn */ + namespace = talloc_strdup(ctx, p + 1); + if (namespace == NULL) { + goto fail; + } + domain = talloc_strdup(ctx, ""); + if (domain == NULL) { + goto fail; + } + + } else if (assume_domain(lp_workgroup())) { + domain = talloc_strdup(ctx, lp_workgroup()); + if (domain == NULL) { + goto fail; + } + namespace = talloc_strdup(ctx, domain); + if (namespace == NULL) { + goto fail; + } + } else { + namespace = talloc_strdup(ctx, lp_netbios_name()); + if (namespace == NULL) { + goto fail; + } + domain = talloc_strdup(ctx, ""); + if (domain == NULL) { + goto fail; + } + } + } + + if (!strupper_m(domain)) { + goto fail; + } + + *pnamespace = namespace; + *pdomain = domain; + *puser = user; + return true; +fail: + TALLOC_FREE(user); + TALLOC_FREE(domain); + TALLOC_FREE(namespace); + return false; +} + +bool canonicalize_username(TALLOC_CTX *mem_ctx, + char **pusername_inout, + char **pnamespace, + char **pdomain, + char **puser) +{ + bool ok; + char *namespace = NULL; + char *domain = NULL; + char *user = NULL; + char *username_inout = NULL; + + ok = parse_domain_user(mem_ctx, + *pusername_inout, + &namespace, &domain, &user); + + if (!ok) { + return False; + } + + username_inout = talloc_asprintf(mem_ctx, "%s%c%s", + domain, *lp_winbind_separator(), + user); + + if (username_inout == NULL) { + goto fail; + } + + *pnamespace = namespace; + *puser = user; + *pdomain = domain; + *pusername_inout = username_inout; + return True; +fail: + TALLOC_FREE(username_inout); + TALLOC_FREE(namespace); + TALLOC_FREE(domain); + TALLOC_FREE(user); + return false; +} + +/* + Fill DOMAIN\\USERNAME entry accounting 'winbind use default domain' and + 'winbind separator' options. + This means: + - omit DOMAIN when 'winbind use default domain = true' and DOMAIN is + lp_workgroup() + + If we are a PDC or BDC, and this is for our domain, do likewise. + + On an AD DC we always fill DOMAIN\\USERNAME. + + We always canonicalize as UPPERCASE DOMAIN, lowercase username. +*/ +/** + * 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; + + if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) { + can_assume = false; + } + + if (user == NULL) { + return NULL; + } + + tmp_user = talloc_strdup(mem_ctx, user); + if (tmp_user == NULL) { + return NULL; + } + if (!strlower_m(tmp_user)) { + TALLOC_FREE(tmp_user); + return NULL; + } + + 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; +} + +/* + * Client list accessor functions + */ + +static struct winbindd_cli_state *_client_list; +static int _num_clients; + +/* Return list of all connected clients */ + +struct winbindd_cli_state *winbindd_client_list(void) +{ + return _client_list; +} + +/* Return list-tail of all connected clients */ + +struct winbindd_cli_state *winbindd_client_list_tail(void) +{ + return DLIST_TAIL(_client_list); +} + +/* Return previous (read:newer) client in list */ + +struct winbindd_cli_state * +winbindd_client_list_prev(struct winbindd_cli_state *cli) +{ + return DLIST_PREV(cli); +} + +/* Add a connection to the list */ + +void winbindd_add_client(struct winbindd_cli_state *cli) +{ + cli->last_access = time(NULL); + DLIST_ADD(_client_list, cli); + _num_clients++; +} + +/* Remove a client from the list */ + +void winbindd_remove_client(struct winbindd_cli_state *cli) +{ + DLIST_REMOVE(_client_list, cli); + _num_clients--; +} + +/* Move a client to head or list */ + +void winbindd_promote_client(struct winbindd_cli_state *cli) +{ + cli->last_access = time(NULL); + DLIST_PROMOTE(_client_list, cli); +} + +/* Return number of open clients */ + +int winbindd_num_clients(void) +{ + return _num_clients; +} + +NTSTATUS lookup_usergroups_cached(TALLOC_CTX *mem_ctx, + const struct dom_sid *user_sid, + uint32_t *p_num_groups, struct dom_sid **user_sids) +{ + struct netr_SamInfo3 *info3 = NULL; + NTSTATUS status = NT_STATUS_NO_MEMORY; + uint32_t num_groups = 0; + + DEBUG(3,(": lookup_usergroups_cached\n")); + + *user_sids = NULL; + *p_num_groups = 0; + + info3 = netsamlogon_cache_get(mem_ctx, user_sid); + + if (info3 == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + /* + * Before bug #7843 the "Domain Local" groups were added with a + * lookupuseraliases call, but this isn't done anymore for our domain + * so we need to resolve resource groups here. + * + * When to use Resource Groups: + * http://technet.microsoft.com/en-us/library/cc753670%28v=WS.10%29.aspx + */ + status = sid_array_from_info3(mem_ctx, info3, + user_sids, + &num_groups, + false); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(info3); + return status; + } + + TALLOC_FREE(info3); + *p_num_groups = num_groups; + status = (user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY; + + DEBUG(3,(": lookup_usergroups_cached succeeded\n")); + + return status; +} + +/********************************************************************* + We use this to remove spaces from user and group names +********************************************************************/ + +NTSTATUS normalize_name_map(TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + char **normalized) +{ + struct winbindd_domain *domain = NULL; + NTSTATUS nt_status; + + if (!name || !normalized) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!lp_winbind_normalize_names()) { + return NT_STATUS_PROCEDURE_NOT_FOUND; + } + + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + DBG_ERR("Failed to find domain '%s'\n", domain_name); + return NT_STATUS_NO_SUCH_DOMAIN; + } + + /* Alias support and whitespace replacement are mutually + exclusive */ + + 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 normalize_name_map() +********************************************************************/ + +NTSTATUS normalize_name_unmap(TALLOC_CTX *mem_ctx, + const char *name, + char **normalized) +{ + NTSTATUS nt_status; + struct winbindd_domain *domain = find_our_domain(); + + if (!name || !normalized) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!lp_winbind_normalize_names()) { + return NT_STATUS_PROCEDURE_NOT_FOUND; + } + + /* 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; +} + +/********************************************************************* + ********************************************************************/ + +bool winbindd_can_contact_domain(struct winbindd_domain *domain) +{ + struct winbindd_tdc_domain *tdc = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + bool ret = false; + + /* We can contact the domain if it is our primary domain */ + + if (domain->primary) { + ret = true; + goto done; + } + + /* Trust the TDC cache and not the winbindd_domain flags */ + + if ((tdc = wcache_tdc_fetch_domain(frame, domain->name)) == NULL) { + DEBUG(10,("winbindd_can_contact_domain: %s not found in cache\n", + domain->name)); + ret = false; + goto done; + } + + /* Can always contact a domain that is in out forest */ + + if (tdc->trust_flags & NETR_TRUST_FLAG_IN_FOREST) { + ret = true; + goto done; + } + + /* + * On a _member_ server, we cannot contact the domain if it + * is running AD and we have no inbound trust. + */ + + if (!IS_DC && + domain->active_directory && + ((tdc->trust_flags & NETR_TRUST_FLAG_INBOUND) != NETR_TRUST_FLAG_INBOUND)) + { + DEBUG(10, ("winbindd_can_contact_domain: %s is an AD domain " + "and we have no inbound trust.\n", domain->name)); + goto done; + } + + /* Assume everything else is ok (probably not true but what + can you do?) */ + + ret = true; + +done: + talloc_destroy(frame); + + return ret; +} + +#ifdef HAVE_KRB5_LOCATE_PLUGIN_H + +/********************************************************************* + ********************************************************************/ + +static void winbindd_set_locator_kdc_env(const struct winbindd_domain *domain) +{ + char *var = NULL; + char addr[INET6_ADDRSTRLEN]; + const char *kdc = NULL; + int lvl = 11; + + if (!domain || !domain->alt_name || !*domain->alt_name) { + return; + } + + if (domain->initialized && !domain->active_directory) { + DEBUG(lvl,("winbindd_set_locator_kdc_env: %s not AD\n", + domain->alt_name)); + return; + } + + print_sockaddr(addr, sizeof(addr), &domain->dcaddr); + kdc = addr; + if (!*kdc) { + DEBUG(lvl,("winbindd_set_locator_kdc_env: %s no DC IP\n", + domain->alt_name)); + kdc = domain->dcname; + } + + if (!kdc || !*kdc) { + DEBUG(lvl,("winbindd_set_locator_kdc_env: %s no DC at all\n", + domain->alt_name)); + return; + } + + var = talloc_asprintf_strupper_m( + talloc_tos(), + "%s_%s", + WINBINDD_LOCATOR_KDC_ADDRESS, + domain->alt_name); + if (var == NULL) { + return; + } + + DEBUG(lvl,("winbindd_set_locator_kdc_env: setting var: %s to: %s\n", + var, kdc)); + + setenv(var, kdc, 1); + TALLOC_FREE(var); +} + +/********************************************************************* + ********************************************************************/ + +void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain) +{ + struct winbindd_domain *our_dom = find_our_domain(); + + winbindd_set_locator_kdc_env(domain); + + if (domain != our_dom) { + winbindd_set_locator_kdc_env(our_dom); + } +} + +/********************************************************************* + ********************************************************************/ + +void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain) +{ + char *var = NULL; + + if (!domain || !domain->alt_name || !*domain->alt_name) { + return; + } + + var = talloc_asprintf_strupper_m( + talloc_tos(), + "%s_%s", + WINBINDD_LOCATOR_KDC_ADDRESS, + domain->alt_name); + if (var == NULL) { + return; + } + + unsetenv(var); + TALLOC_FREE(var); +} +#else + +void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain) +{ + return; +} + +void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain) +{ + return; +} + +#endif /* HAVE_KRB5_LOCATE_PLUGIN_H */ + +void set_auth_errors(struct winbindd_response *resp, NTSTATUS result) +{ + /* + * Make sure we start with authoritative=true, + * it will only set to false if we don't know the + * domain. + */ + resp->data.auth.authoritative = true; + + resp->data.auth.nt_status = NT_STATUS_V(result); + fstrcpy(resp->data.auth.nt_status_string, nt_errstr(result)); + + /* we might have given a more useful error above */ + if (*resp->data.auth.error_string == '\0') + fstrcpy(resp->data.auth.error_string, + get_friendly_nt_error_msg(result)); + resp->data.auth.pam_error = nt_status_to_pam(result); +} + +bool is_domain_offline(const struct winbindd_domain *domain) +{ + if (get_global_winbindd_state_offline()) { + return true; + } + return !domain->online; +} + +bool is_domain_online(const struct winbindd_domain *domain) +{ + return !is_domain_offline(domain); +} + +/** + * Parse an char array into a list of sids. + * + * The input sidstr should consist of 0-terminated strings + * representing sids, separated by newline characters '\n'. + * The list is terminated by an empty string, i.e. + * character '\0' directly following a character '\n' + * (or '\0' right at the start of sidstr). + */ +bool parse_sidlist(TALLOC_CTX *mem_ctx, const char *sidstr, + struct dom_sid **sids, uint32_t *num_sids) +{ + const char *p; + + p = sidstr; + if (p == NULL) + return False; + + while (p[0] != '\0') { + struct dom_sid sid; + const char *q = NULL; + + if (!dom_sid_parse_endp(p, &sid, &q)) { + DEBUG(1, ("Could not parse sid %s\n", p)); + return false; + } + if (q[0] != '\n') { + DEBUG(1, ("Got invalid sidstr: %s\n", p)); + return false; + } + if (!NT_STATUS_IS_OK(add_sid_to_array(mem_ctx, &sid, sids, + num_sids))) + { + return False; + } + p = q+1; + } + return True; +} + +bool parse_xidlist(TALLOC_CTX *mem_ctx, const char *xidstr, + struct unixid **pxids, uint32_t *pnum_xids) +{ + const char *p; + struct unixid *xids = NULL; + uint32_t num_xids = 0; + + p = xidstr; + if (p == NULL) { + return false; + } + + while (p[0] != '\0') { + struct unixid *tmp; + struct unixid xid; + unsigned long long id; + char *endp; + int error = 0; + + switch (p[0]) { + case 'U': + xid = (struct unixid) { .type = ID_TYPE_UID }; + break; + case 'G': + xid = (struct unixid) { .type = ID_TYPE_GID }; + break; + default: + return false; + } + + p += 1; + + id = smb_strtoull(p, &endp, 10, &error, SMB_STR_STANDARD); + if (error != 0) { + goto fail; + } + if (*endp != '\n') { + goto fail; + } + p = endp+1; + + xid.id = id; + if ((unsigned long long)xid.id != id) { + goto fail; + } + + tmp = talloc_realloc(mem_ctx, xids, struct unixid, num_xids+1); + if (tmp == NULL) { + return 0; + } + xids = tmp; + + xids[num_xids] = xid; + num_xids += 1; + } + + *pxids = xids; + *pnum_xids = num_xids; + return true; + +fail: + TALLOC_FREE(xids); + return false; +} diff --git a/source3/winbindd/winbindd_wins_byip.c b/source3/winbindd/winbindd_wins_byip.c new file mode 100644 index 0000000..1b9cdbc --- /dev/null +++ b/source3/winbindd/winbindd_wins_byip.c @@ -0,0 +1,142 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_WINS_BYIP + Copyright (C) Volker Lendecke 2011 + + 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.h" +#include "libsmb/namequery.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "libsmb/nmblib.h" +#include "lib/util/string_wrappers.h" + +struct winbindd_wins_byip_state { + struct nmb_name star; + struct sockaddr_storage addr; + fstring response; +}; + +static void winbindd_wins_byip_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_wins_byip_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_wins_byip_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_wins_byip_state); + if (req == NULL) { + return NULL; + } + /* Ensure null termination */ + request->data.winsreq[sizeof(request->data.winsreq)-1]='\0'; + + fstr_sprintf(state->response, "%s\t", request->data.winsreq); + + D_NOTICE("[%s (%u)] Winbind external command WINS_BYIP start.\n" + "Resolving wins byip for %s.\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.winsreq); + + make_nmb_name(&state->star, "*", 0); + + if (!interpret_string_addr(&state->addr, request->data.winsreq, + AI_NUMERICHOST)) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + subreq = node_status_query_send(state, ev, &state->star, + &state->addr); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_wins_byip_done, req); + return req; +} + +static void winbindd_wins_byip_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_wins_byip_state *state = tevent_req_data( + req, struct winbindd_wins_byip_state); + struct node_status *names; + size_t i; + size_t num_names = 0; + NTSTATUS status; + + status = node_status_query_recv(subreq, talloc_tos(), &names, + &num_names, NULL); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + for (i=0; i<num_names; i++) { + size_t size; + /* + * ignore group names + */ + if (names[i].flags & 0x80) { + continue; + } + /* + * Only report 0x20 + */ + if (names[i].type != 0x20) { + continue; + } + + D_DEBUG("Got name '%s'.\n", names[i].name); + + size = strlen(names[i].name + strlen(state->response)); + if (size > sizeof(state->response) - 1) { + D_WARNING("Too much data!\n"); + tevent_req_nterror(req, STATUS_BUFFER_OVERFLOW); + return; + } + fstrcat(state->response, names[i].name); + fstrcat(state->response, " "); + } + state->response[strlen(state->response)-1] = '\n'; + + + TALLOC_FREE(names); + tevent_req_done(req); +} + +NTSTATUS winbindd_wins_byip_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + struct winbindd_wins_byip_state *state = tevent_req_data( + req, struct winbindd_wins_byip_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + D_NOTICE("Winbind external command WINS_BYIP end.\n" + "Response: %s", + state->response); + fstrcpy(presp->data.winsresp, state->response); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_wins_byname.c b/source3/winbindd/winbindd_wins_byname.c new file mode 100644 index 0000000..ae170b2 --- /dev/null +++ b/source3/winbindd/winbindd_wins_byname.c @@ -0,0 +1,154 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_WINS_BYNAME + Copyright (C) Volker Lendecke 2011 + + 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.h" +#include "libsmb/namequery.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "libsmb/nmblib.h" +#include "lib/util/string_wrappers.h" + +struct winbindd_wins_byname_state { + struct tevent_context *ev; + struct winbindd_request *request; + struct sockaddr_storage *addrs; + size_t num_addrs; +}; + +static void winbindd_wins_byname_wins_done(struct tevent_req *subreq); +static void winbindd_wins_byname_bcast_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_wins_byname_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_wins_byname_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_wins_byname_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->request = request; + + /* Ensure null termination */ + request->data.winsreq[sizeof(request->data.winsreq)-1]='\0'; + + D_NOTICE("[%s (%u)] Winbind external command WINS_BYNAME start.\n" + "Resolving wins byname for '%s'.\n", + cli->client_name, + (unsigned int)cli->pid, + request->data.winsreq); + + subreq = resolve_wins_send(state, ev, state->request->data.winsreq, + 0x20); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_wins_byname_wins_done, req); + return req; +} + +static void winbindd_wins_byname_wins_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_wins_byname_state *state = tevent_req_data( + req, struct winbindd_wins_byname_state); + NTSTATUS status; + + status = resolve_wins_recv(subreq, talloc_tos(), &state->addrs, + &state->num_addrs, NULL); + TALLOC_FREE(subreq); + if (NT_STATUS_IS_OK(status)) { + tevent_req_done(req); + return; + } + subreq = name_resolve_bcast_send(state, state->ev, + state->request->data.winsreq, 0x20); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, winbindd_wins_byname_bcast_done, req); +} + +static void winbindd_wins_byname_bcast_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_wins_byname_state *state = tevent_req_data( + req, struct winbindd_wins_byname_state); + NTSTATUS status; + + status = name_resolve_bcast_recv(subreq, talloc_tos(), &state->addrs, + &state->num_addrs); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_wins_byname_recv(struct tevent_req *req, + struct winbindd_response *presp) +{ + struct winbindd_wins_byname_state *state = tevent_req_data( + req, struct winbindd_wins_byname_state); + char *response; + NTSTATUS status; + size_t i; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + + response = talloc_strdup(talloc_tos(), ""); + if (response == NULL) { + return NT_STATUS_NO_MEMORY; + } + + D_NOTICE("Winbind external command WINS_BYNAME end.\n" + "Received %zu address(es).\n", + state->num_addrs); + for (i=0; i<state->num_addrs; i++) { + char addr[INET6_ADDRSTRLEN]; + print_sockaddr(addr, sizeof(addr), &state->addrs[i]); + D_NOTICE("%zu: %s\n", i, addr); + talloc_asprintf_addbuf( + &response, "%s%s", addr, + i < (state->num_addrs-1) ? " " : ""); + } + + talloc_asprintf_addbuf( + &response, "\t%s\n", state->request->data.winsreq); + if (response == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (talloc_get_size(response) > sizeof(presp->data.winsresp)) { + TALLOC_FREE(response); + return NT_STATUS_MARSHALL_OVERFLOW; + } + fstrcpy(presp->data.winsresp, response); + TALLOC_FREE(response); + return NT_STATUS_OK; +} diff --git a/source3/winbindd/winbindd_xids_to_sids.c b/source3/winbindd/winbindd_xids_to_sids.c new file mode 100644 index 0000000..3420377 --- /dev/null +++ b/source3/winbindd/winbindd_xids_to_sids.c @@ -0,0 +1,142 @@ +/* + Unix SMB/CIFS implementation. + async implementation of WINBINDD_SIDS_TO_XIDS + Copyright (C) Volker Lendecke 2011 + Copyright (C) Michael Adam 2012 + + 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.h" +#include "../libcli/security/security.h" + + +struct winbindd_xids_to_sids_state { + struct tevent_context *ev; + + struct unixid *xids; + uint32_t num_xids; + + struct dom_sid *sids; +}; + +static void winbindd_xids_to_sids_done(struct tevent_req *subreq); + +struct tevent_req *winbindd_xids_to_sids_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct winbindd_cli_state *cli, + struct winbindd_request *request) +{ + struct tevent_req *req, *subreq; + struct winbindd_xids_to_sids_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct winbindd_xids_to_sids_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + + D_NOTICE("[%s (%u)] Winbind external command XIDS_TO_SIDS start.\n", + cli->client_name, + (unsigned int)cli->pid); + + if (request->extra_len == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (request->extra_data.data[request->extra_len-1] != '\0') { + D_DEBUG("Got invalid XID list.\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + if (!parse_xidlist(state, request->extra_data.data, + &state->xids, &state->num_xids)) { + D_DEBUG("parse_sidlist failed\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + D_DEBUG("Resolving %"PRIu32" XID(s):\n%s\n", + state->num_xids, + (char *)request->extra_data.data); + + subreq = wb_xids2sids_send(state, ev, state->xids, state->num_xids); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, winbindd_xids_to_sids_done, req); + return req; +} + +static void winbindd_xids_to_sids_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct winbindd_xids_to_sids_state *state = tevent_req_data( + req, struct winbindd_xids_to_sids_state); + NTSTATUS status; + + status = wb_xids2sids_recv(subreq, state, &state->sids); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +NTSTATUS winbindd_xids_to_sids_recv(struct tevent_req *req, + struct winbindd_response *response) +{ + struct winbindd_xids_to_sids_state *state = tevent_req_data( + req, struct winbindd_xids_to_sids_state); + NTSTATUS status; + char *result = NULL; + uint32_t i; + + D_NOTICE("Winbind external command XIDS_TO_SIDS end.\n"); + if (tevent_req_is_nterror(req, &status)) { + D_WARNING("Could not convert xids: %s\n", nt_errstr(status)); + return status; + } + + result = talloc_strdup(response, ""); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<state->num_xids; i++) { + struct dom_sid_buf sid_buf; + const char *str = "-"; + + if (!is_null_sid(&state->sids[i])) { + dom_sid_str_buf(&state->sids[i], &sid_buf); + str = sid_buf.buf; + } + + D_NOTICE("%"PRIu32": XID %"PRIu32" mapped to SID %s.\n", + i, state->xids[i].id, str); + result = talloc_asprintf_append_buffer( + result, "%s\n", str); + if (result == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + response->extra_data.data = result; + response->length += talloc_get_size(result); + + return NT_STATUS_OK; +} diff --git a/source3/winbindd/wscript_build b/source3/winbindd/wscript_build new file mode 100644 index 0000000..4c85876 --- /dev/null +++ b/source3/winbindd/wscript_build @@ -0,0 +1,291 @@ +#!/usr/bin/env python + +bld.SAMBA3_LIBRARY('idmap', + source='idmap.c idmap_util.c', + deps='samba-util pdb', + allow_undefined_symbols=True, + private_library=True) + +bld.SAMBA3_SUBSYSTEM('IDMAP_RW', + source='idmap_rw.c', + deps='samba-util') + +bld.SAMBA3_SUBSYSTEM('IDMAP_TDB_COMMON', + source='idmap_tdb_common.c', + deps='tdb IDMAP_RW') + +bld.SAMBA3_SUBSYSTEM('IDMAP_HASH', + source='idmap_hash/idmap_hash.c idmap_hash/mapfile.c', + deps='samba-util krb5samba', + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_hash')) + +bld.SAMBA3_SUBSYSTEM('IDMAP_AD', + source='idmap_ad.c idmap_ad_nss.c', + deps='ads nss_info', + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad')) + +bld.SAMBA3_MODULE('idmap_ad', + subsystem='idmap', + allow_undefined_symbols=True, + source='', + deps='IDMAP_AD TLDAP LIBNMB', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ad'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad')) + +bld.SAMBA3_MODULE('idmap_rfc2307', + subsystem='idmap', + allow_undefined_symbols=True, + source='idmap_rfc2307.c', + init_function='', + deps='ads', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_rfc2307'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_rfc2307')) + +bld.SAMBA3_MODULE('idmap_rid', + subsystem='idmap', + allow_undefined_symbols=True, + source='idmap_rid.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_rid'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_rid')) + +bld.SAMBA3_MODULE('idmap_passdb', + subsystem='idmap', + source='idmap_passdb.c', + deps='samba-util samba-passdb', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_passdb'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_passdb')) + +bld.SAMBA3_MODULE('idmap_ldap', + subsystem='idmap', + source='idmap_ldap.c', + deps='smbldap smbldaphelper pdb IDMAP_RW', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ldap'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ldap') and bld.CONFIG_SET("HAVE_LDAP"), + allow_undefined_symbols=True) + +bld.SAMBA3_MODULE('idmap_nss', + subsystem='idmap', + source='idmap_nss.c', + deps='samba-util', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_nss'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_nss')) + +bld.SAMBA3_MODULE('idmap_tdb', + subsystem='idmap', + source='idmap_tdb.c', + deps='samba-util tdb IDMAP_TDB_COMMON', + init_function='', + allow_undefined_symbols=True, + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_tdb'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_tdb')) + +bld.SAMBA3_MODULE('idmap_tdb2', + subsystem='idmap', + source='idmap_tdb2.c', + deps='samba-util tdb IDMAP_TDB_COMMON', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_tdb2'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_tdb2')) + +bld.SAMBA3_MODULE('idmap_hash', + subsystem='idmap', + source='', + deps='IDMAP_HASH', + allow_undefined_symbols=True, + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_hash'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_hash')) + +bld.SAMBA3_SUBSYSTEM('IDMAP_AUTORID_TDB', + source='idmap_autorid_tdb.c', + deps='tdb') + +bld.SAMBA3_MODULE('idmap_autorid', + subsystem='idmap', + source='idmap_autorid.c', + deps='samba-util tdb IDMAP_TDB_COMMON IDMAP_AUTORID_TDB', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_autorid'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_autorid'), + allow_undefined_symbols=True) + +bld.SAMBA3_LIBRARY('nss_info', + source='nss_info.c', + deps='samba-util smbconf samba-modules', + private_library=True) + +bld.SAMBA3_MODULE('nss_info_template', + subsystem='nss_info', + source='nss_info_template.c', + deps='samba-util krb5samba', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('nss_info_template'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('nss_info_template')) + +bld.SAMBA3_MODULE('nss_info_hash', + subsystem='nss_info', + source='', + deps='IDMAP_HASH', + allow_undefined_symbols=True, + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_hash'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_hash')) + +bld.SAMBA3_MODULE('nss_info_rfc2307', + subsystem='nss_info', + source='', + deps='IDMAP_AD', + allow_undefined_symbols=True, + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ad'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad')) + +bld.SAMBA3_MODULE('nss_info_sfu20', + subsystem='nss_info', + source='', + deps='IDMAP_AD', + allow_undefined_symbols=True, + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ad'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad')) + +bld.SAMBA3_MODULE('nss_info_sfu', + subsystem='nss_info', + source='', + deps='IDMAP_AD', + allow_undefined_symbols=True, + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ad'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad') and bld.CONFIG_SET("HAVE_LDAP")) + +bld.SAMBA3_MODULE('idmap_script', + subsystem='idmap', + allow_undefined_symbols=True, + source='idmap_script.c', + init_function='', + internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_script'), + enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_script')) + +bld.SAMBA3_SUBSYSTEM('winbindd-lib', + source=''' + winbindd_group.c + winbindd_util.c + winbindd_cache.c + winbindd_pam.c + winbindd_misc.c + winbindd_cm.c + winbindd_wins_byip.c + winbindd_wins_byname.c + winbindd_msrpc.c + winbindd_rpc.c + winbindd_reconnect.c + winbindd_reconnect_ads.c + winbindd_ads.c + winbindd_samr.c + winbindd_dual.c + winbindd_dual_ndr.c + winbindd_dual_srv.c + winbindd_creds.c + winbindd_cred_cache.c + winbindd_ccache_access.c + winbindd_domain.c + winbindd_idmap.c + winbindd_locator.c + winbindd_ndr.c + winbindd_traceid.c + wb_lookupsid.c + wb_lookupsids.c + wb_lookupname.c + wb_sids2xids.c + wb_xids2sids.c + wb_queryuser.c + wb_lookupuseraliases.c + wb_lookupusergroups.c + wb_getpwsid.c + wb_gettoken.c + wb_seqnum.c + wb_seqnums.c + wb_group_members.c + wb_alias_members.c + wb_getgrsid.c + wb_query_user_list.c + wb_query_group_list.c + wb_next_pwent.c + wb_next_grent.c + wb_dsgetdcname.c + winbindd_lookupsid.c + winbindd_lookupsids.c + winbindd_lookupname.c + winbindd_sids_to_xids.c + winbindd_xids_to_sids.c + winbindd_allocate_uid.c + winbindd_allocate_gid.c + winbindd_getpwsid.c + winbindd_getpwnam.c + winbindd_getpwuid.c + winbindd_getsidaliases.c + winbindd_getuserdomgroups.c + winbindd_getgroups.c + winbindd_show_sequence.c + winbindd_getgrgid.c + winbindd_getgrnam.c + winbindd_getusersids.c + winbindd_lookuprids.c + winbindd_setpwent.c + winbindd_getpwent.c + winbindd_endpwent.c + winbindd_setgrent.c + winbindd_getgrent.c + winbindd_endgrent.c + winbindd_dsgetdcname.c + winbindd_getdcname.c + winbindd_list_users.c + winbindd_list_groups.c + winbindd_check_machine_acct.c + winbindd_change_machine_acct.c + winbindd_irpc.c + winbindd_ping_dc.c + winbindd_domain_info.c + winbindd_pam_auth.c + winbindd_pam_logoff.c + winbindd_pam_chauthtok.c + winbindd_pam_auth_crap.c + winbindd_pam_chng_pswd_auth_crap.c + winbindd_gpupdate.c''', + deps=''' + talloc + tevent + pdb + idmap + ads + msrpc3 + nss_info + LIBAFS + LIBADS_SERVER + LIBCLI_SAMR + SLCACHE + RPC_NDR_DSSETUP + RPC_NDR_WINBIND + TOKEN_UTIL + RPC_SERVER + WB_REQTRANS + TDB_VALIDATE + MESSAGING + LIBLSA + ''') + +bld.SAMBA3_BINARY('winbindd', + source=''' + winbindd.c + ''', + deps=''' + CMDLINE_S3 + winbindd-lib + ''', + enabled=bld.env.build_winbind, + install_path='${SBINDIR}') |