diff options
Diffstat (limited to '')
-rw-r--r-- | source4/dsdb/samdb/cracknames.c | 1722 |
1 files changed, 1722 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/cracknames.c b/source4/dsdb/samdb/cracknames.c new file mode 100644 index 0000000..4852a0e --- /dev/null +++ b/source4/dsdb/samdb/cracknames.c @@ -0,0 +1,1722 @@ +/* + Unix SMB/CIFS implementation. + + crachnames implementation for the drsuapi pipe + DsCrackNames() + + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Matthieu Patou <mat@matws.net> 2012 + Copyright (C) Catalyst .Net Ltd 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 "librpc/gen_ndr/drsuapi.h" +#include "lib/events/events.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "auth/kerberos/kerberos.h" +#include "libcli/ldap/ldap_ndr.h" +#include "libcli/security/security.h" +#include "auth/auth.h" +#include "../lib/util/util_ldb.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "param/param.h" + +#undef strcasecmp + +static WERROR DsCrackNameOneFilter(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + uint32_t format_flags, enum drsuapi_DsNameFormat format_offered, + enum drsuapi_DsNameFormat format_desired, + struct ldb_dn *name_dn, const char *name, + const char *domain_filter, const char *result_filter, + struct drsuapi_DsNameInfo1 *info1, int scope, struct ldb_dn *search_dn); +static WERROR DsCrackNameOneSyntactical(TALLOC_CTX *mem_ctx, + enum drsuapi_DsNameFormat format_offered, + enum drsuapi_DsNameFormat format_desired, + struct ldb_dn *name_dn, const char *name, + struct drsuapi_DsNameInfo1 *info1); + +static WERROR dns_domain_from_principal(TALLOC_CTX *mem_ctx, struct smb_krb5_context *smb_krb5_context, + const char *name, + struct drsuapi_DsNameInfo1 *info1) +{ + krb5_error_code ret; + krb5_principal principal; + /* perhaps it's a principal with a realm, so return the right 'domain only' response */ + ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name, + KRB5_PRINCIPAL_PARSE_REQUIRE_REALM, &principal); + if (ret) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + + info1->dns_domain_name = smb_krb5_principal_get_realm( + mem_ctx, smb_krb5_context->krb5_context, principal); + krb5_free_principal(smb_krb5_context->krb5_context, principal); + + W_ERROR_HAVE_NO_MEMORY(info1->dns_domain_name); + + info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY; + return WERR_OK; +} + +static enum drsuapi_DsNameStatus LDB_lookup_spn_alias(struct ldb_context *ldb_ctx, + TALLOC_CTX *mem_ctx, + const char *alias_from, + char **alias_to) +{ + /* + * Some of the logic of this function is mirrored in find_spn_alias() + * in source4/dsdb.samdb/ldb_modules/samldb.c. If you change this to + * not return the first matched alias, you will need to rethink that + * function too. + */ + unsigned int i; + int ret; + struct ldb_result *res; + struct ldb_message_element *spnmappings; + TALLOC_CTX *tmp_ctx; + struct ldb_dn *service_dn; + char *service_dn_str; + + const char *directory_attrs[] = { + "sPNMappings", + NULL + }; + + tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + } + + service_dn = ldb_dn_new(tmp_ctx, ldb_ctx, "CN=Directory Service,CN=Windows NT,CN=Services"); + if ( ! ldb_dn_add_base(service_dn, ldb_get_config_basedn(ldb_ctx))) { + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + } + service_dn_str = ldb_dn_alloc_linearized(tmp_ctx, service_dn); + if ( ! service_dn_str) { + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + } + + ret = ldb_search(ldb_ctx, tmp_ctx, &res, service_dn, LDB_SCOPE_BASE, + directory_attrs, "(objectClass=nTDSService)"); + + if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(1, ("ldb_search: dn: %s not found: %s\n", service_dn_str, ldb_errstring(ldb_ctx))); + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + } else if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(1, ("ldb_search: dn: %s not found\n", service_dn_str)); + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + } else if (res->count != 1) { + DEBUG(1, ("ldb_search: dn: %s not found\n", service_dn_str)); + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + } + + spnmappings = ldb_msg_find_element(res->msgs[0], "sPNMappings"); + if (!spnmappings || spnmappings->num_values == 0) { + DEBUG(1, ("ldb_search: dn: %s no sPNMappings attribute\n", service_dn_str)); + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + } + + for (i = 0; i < spnmappings->num_values; i++) { + char *mapping, *p, *str; + mapping = talloc_strdup(tmp_ctx, + (const char *)spnmappings->values[i].data); + if (!mapping) { + DEBUG(1, ("LDB_lookup_spn_alias: ldb_search: dn: %s did not have an sPNMapping\n", service_dn_str)); + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + } + + /* C string manipulation sucks */ + + p = strchr(mapping, '='); + if (!p) { + DEBUG(1, ("ldb_search: dn: %s sPNMapping malformed: %s\n", + service_dn_str, mapping)); + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + } + p[0] = '\0'; + p++; + do { + str = p; + p = strchr(p, ','); + if (p) { + p[0] = '\0'; + p++; + } + if (strcasecmp(str, alias_from) == 0) { + *alias_to = mapping; + talloc_steal(mem_ctx, mapping); + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_OK; + } + } while (p); + } + DEBUG(4, ("LDB_lookup_spn_alias: no alias for service %s applicable\n", alias_from)); + talloc_free(tmp_ctx); + return DRSUAPI_DS_NAME_STATUS_NOT_FOUND; +} + +/* When cracking a ServicePrincipalName, many services may be served + * by the host/ servicePrincipalName. The incoming query is for cifs/ + * but we translate it here, and search on host/. This is done after + * the cifs/ entry has been searched for, making this a fallback */ + +static WERROR DsCrackNameSPNAlias(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + uint32_t format_flags, enum drsuapi_DsNameFormat format_offered, + enum drsuapi_DsNameFormat format_desired, + const char *name, struct drsuapi_DsNameInfo1 *info1) +{ + WERROR wret; + krb5_error_code ret; + krb5_principal principal; + const krb5_data *component; + const char *service, *dns_name; + char *new_service; + char *new_princ; + enum drsuapi_DsNameStatus namestatus; + + /* parse principal */ + ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, + name, KRB5_PRINCIPAL_PARSE_NO_REALM, &principal); + if (ret) { + DEBUG(2, ("Could not parse principal: %s: %s\n", + name, smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + return WERR_NOT_ENOUGH_MEMORY; + } + + /* grab cifs/, http/ etc */ + + /* This is checked for in callers, but be safe */ + if (krb5_princ_size(smb_krb5_context->krb5_context, principal) < 2) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return WERR_OK; + } + component = krb5_princ_component(smb_krb5_context->krb5_context, + principal, 0); + service = (const char *)component->data; + component = krb5_princ_component(smb_krb5_context->krb5_context, + principal, 1); + dns_name = (const char *)component->data; + + /* MAP it */ + namestatus = LDB_lookup_spn_alias(sam_ctx, mem_ctx, + service, &new_service); + + if (namestatus == DRSUAPI_DS_NAME_STATUS_NOT_FOUND) { + wret = WERR_OK; + info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY; + info1->dns_domain_name = talloc_strdup(mem_ctx, dns_name); + if (!info1->dns_domain_name) { + wret = WERR_NOT_ENOUGH_MEMORY; + } + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return wret; + } else if (namestatus != DRSUAPI_DS_NAME_STATUS_OK) { + info1->status = namestatus; + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return WERR_OK; + } + + /* reform principal */ + new_princ = talloc_asprintf(mem_ctx, "%s/%s", new_service, dns_name); + if (!new_princ) { + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return WERR_NOT_ENOUGH_MEMORY; + } + + wret = DsCrackNameOneName(sam_ctx, mem_ctx, format_flags, format_offered, format_desired, + new_princ, info1); + talloc_free(new_princ); + if (W_ERROR_IS_OK(wret) && (info1->status == DRSUAPI_DS_NAME_STATUS_NOT_FOUND)) { + info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY; + info1->dns_domain_name = talloc_strdup(mem_ctx, dns_name); + if (!info1->dns_domain_name) { + wret = WERR_NOT_ENOUGH_MEMORY; + } + } + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return wret; +} + +/* Subcase of CrackNames, for the userPrincipalName */ + +static WERROR DsCrackNameUPN(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + uint32_t format_flags, enum drsuapi_DsNameFormat format_offered, + enum drsuapi_DsNameFormat format_desired, + const char *name, struct drsuapi_DsNameInfo1 *info1) +{ + int ldb_ret; + WERROR status; + const char *domain_filter = NULL; + const char *result_filter = NULL; + krb5_error_code ret; + krb5_principal principal; + char *realm; + char *unparsed_name_short; + const char *domain_attrs[] = { NULL }; + struct ldb_result *domain_res = NULL; + + /* Prevent recursion */ + if (!name) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + + ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name, + KRB5_PRINCIPAL_PARSE_REQUIRE_REALM, &principal); + if (ret) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + + realm = smb_krb5_principal_get_realm( + mem_ctx, smb_krb5_context->krb5_context, principal); + + ldb_ret = ldb_search(sam_ctx, mem_ctx, &domain_res, + samdb_partitions_dn(sam_ctx, mem_ctx), + LDB_SCOPE_ONELEVEL, + domain_attrs, + "(&(objectClass=crossRef)(|(dnsRoot=%s)(netbiosName=%s))(systemFlags:%s:=%u))", + ldb_binary_encode_string(mem_ctx, realm), + ldb_binary_encode_string(mem_ctx, realm), + LDB_OID_COMPARATOR_AND, + SYSTEM_FLAG_CR_NTDS_DOMAIN); + TALLOC_FREE(realm); + + if (ldb_ret != LDB_SUCCESS) { + DEBUG(2, ("DsCrackNameUPN domain ref search failed: %s\n", ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return WERR_OK; + } + + switch (domain_res->count) { + case 1: + break; + case 0: + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return dns_domain_from_principal(mem_ctx, smb_krb5_context, + name, info1); + default: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE; + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return WERR_OK; + } + + /* + * The important thing here is that a samAccountName may have + * a space in it, and this must not be kerberos escaped to + * match this filter, so we specify + * KRB5_PRINCIPAL_UNPARSE_DISPLAY + */ + ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM | + KRB5_PRINCIPAL_UNPARSE_DISPLAY, + &unparsed_name_short); + krb5_free_principal(smb_krb5_context->krb5_context, principal); + + if (ret) { + free(unparsed_name_short); + return WERR_NOT_ENOUGH_MEMORY; + } + + /* This may need to be extended for more userPrincipalName variations */ + result_filter = talloc_asprintf(mem_ctx, "(&(samAccountName=%s)(objectClass=user))", + ldb_binary_encode_string(mem_ctx, unparsed_name_short)); + + domain_filter = talloc_asprintf(mem_ctx, "(distinguishedName=%s)", ldb_dn_get_linearized(domain_res->msgs[0]->dn)); + + if (!result_filter || !domain_filter) { + free(unparsed_name_short); + return WERR_NOT_ENOUGH_MEMORY; + } + status = DsCrackNameOneFilter(sam_ctx, mem_ctx, + smb_krb5_context, + format_flags, format_offered, format_desired, + NULL, unparsed_name_short, domain_filter, result_filter, + info1, LDB_SCOPE_SUBTREE, NULL); + free(unparsed_name_short); + + return status; +} + +/* + * This function will workout the filtering parameter in order to be able to do + * the adapted search when the incomming format is format_functional. + * This boils down to defining the search_dn (passed as pointer to ldb_dn *) and the + * ldap filter request. + * Main input parameters are: + * * name, which is the portion of the functional name after the + * first '/'. + * * domain_filter, which is a ldap search filter used to find the NC DN given the + * function name to crack. + */ +static WERROR get_format_functional_filtering_param(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + char *name, struct drsuapi_DsNameInfo1 *info1, + struct ldb_dn **psearch_dn, const char *domain_filter, const char **presult_filter) +{ + struct ldb_result *domain_res = NULL; + const char * const domain_attrs[] = {"ncName", NULL}; + struct ldb_dn *partitions_basedn = samdb_partitions_dn(sam_ctx, mem_ctx); + int ldb_ret; + char *account, *s, *result_filter = NULL; + struct ldb_dn *search_dn = NULL; + + *psearch_dn = NULL; + *presult_filter = NULL; + + ldb_ret = ldb_search(sam_ctx, mem_ctx, &domain_res, + partitions_basedn, + LDB_SCOPE_ONELEVEL, + domain_attrs, + "%s", domain_filter); + + if (ldb_ret != LDB_SUCCESS) { + DEBUG(2, ("DsCrackNameOne domain ref search failed: %s\n", ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_FOOBAR; + } + + if (domain_res->count == 1) { + struct ldb_dn *tmp_dn = samdb_result_dn(sam_ctx, mem_ctx, domain_res->msgs[0], "ncName", NULL); + const char * const name_attrs[] = {"name", NULL}; + + account = name; + s = strchr(account, '/'); + talloc_free(domain_res); + while(s) { + s[0] = '\0'; + s++; + + ldb_ret = ldb_search(sam_ctx, mem_ctx, &domain_res, + tmp_dn, + LDB_SCOPE_ONELEVEL, + name_attrs, + "name=%s", account); + + if (ldb_ret != LDB_SUCCESS) { + DEBUG(2, ("DsCrackNameOne domain ref search failed: %s\n", ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + talloc_free(tmp_dn); + switch (domain_res->count) { + case 1: + break; + case 0: + talloc_free(domain_res); + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + default: + talloc_free(domain_res); + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE; + return WERR_OK; + } + + tmp_dn = talloc_steal(mem_ctx, domain_res->msgs[0]->dn); + talloc_free(domain_res); + search_dn = tmp_dn; + account = s; + s = strchr(account, '/'); + } + account = ldb_binary_encode_string(mem_ctx, account); + W_ERROR_HAVE_NO_MEMORY(account); + result_filter = talloc_asprintf(mem_ctx, "(name=%s)", + account); + W_ERROR_HAVE_NO_MEMORY(result_filter); + } + *psearch_dn = search_dn; + *presult_filter = result_filter; + return WERR_OK; +} + +/* Crack a single 'name', from format_offered into format_desired, returning the result in info1 */ + +WERROR DsCrackNameOneName(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + uint32_t format_flags, enum drsuapi_DsNameFormat format_offered, + enum drsuapi_DsNameFormat format_desired, + const char *name, struct drsuapi_DsNameInfo1 *info1) +{ + krb5_error_code ret; + const char *domain_filter = NULL; + const char *result_filter = NULL; + struct ldb_dn *name_dn = NULL; + struct ldb_dn *search_dn = NULL; + + struct smb_krb5_context *smb_krb5_context = NULL; + int scope = LDB_SCOPE_SUBTREE; + + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + info1->dns_domain_name = NULL; + info1->result_name = NULL; + + if (!name) { + return WERR_INVALID_PARAMETER; + } + + /* TODO: - fill the correct names in all cases! + * - handle format_flags + */ + if (format_desired == DRSUAPI_DS_NAME_FORMAT_UNKNOWN) { + return WERR_OK; + } + /* here we need to set the domain_filter and/or the result_filter */ + switch (format_offered) { + case DRSUAPI_DS_NAME_FORMAT_UNKNOWN: + { + unsigned int i; + enum drsuapi_DsNameFormat formats[] = { + DRSUAPI_DS_NAME_FORMAT_FQDN_1779, DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, DRSUAPI_DS_NAME_FORMAT_CANONICAL, + DRSUAPI_DS_NAME_FORMAT_GUID, DRSUAPI_DS_NAME_FORMAT_DISPLAY, + DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY, + DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX + }; + WERROR werr; + for (i=0; i < ARRAY_SIZE(formats); i++) { + werr = DsCrackNameOneName(sam_ctx, mem_ctx, format_flags, formats[i], format_desired, name, info1); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + if (info1->status != DRSUAPI_DS_NAME_STATUS_NOT_FOUND && + (formats[i] != DRSUAPI_DS_NAME_FORMAT_CANONICAL || + info1->status != DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR)) + { + return werr; + } + } + return werr; + } + + case DRSUAPI_DS_NAME_FORMAT_CANONICAL: + case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX: + { + char *str, *s, *account; + scope = LDB_SCOPE_ONELEVEL; + + if (strlen(name) == 0) { + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + + str = talloc_strdup(mem_ctx, name); + W_ERROR_HAVE_NO_MEMORY(str); + + if (format_offered == DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX) { + /* Look backwards for the \n, and replace it with / */ + s = strrchr(str, '\n'); + if (!s) { + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + s[0] = '/'; + } + + s = strchr(str, '/'); + if (!s) { + /* there must be at least one / */ + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + + s[0] = '\0'; + s++; + + domain_filter = talloc_asprintf(mem_ctx, "(&(objectClass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))", + ldb_binary_encode_string(mem_ctx, str), + LDB_OID_COMPARATOR_AND, + SYSTEM_FLAG_CR_NTDS_DOMAIN); + W_ERROR_HAVE_NO_MEMORY(domain_filter); + + /* There may not be anything after the domain component (search for the domain itself) */ + account = s; + if (account && *account) { + WERROR werr = get_format_functional_filtering_param(sam_ctx, + mem_ctx, + account, + info1, + &search_dn, + domain_filter, + &result_filter); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + if (info1->status != DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR) + return WERR_OK; + } + break; + } + case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT: { + char *p; + char *domain; + const char *account = NULL; + + domain = talloc_strdup(mem_ctx, name); + W_ERROR_HAVE_NO_MEMORY(domain); + + p = strchr(domain, '\\'); + if (!p) { + /* invalid input format */ + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + p[0] = '\0'; + + if (p[1]) { + account = &p[1]; + } + + domain_filter = talloc_asprintf(mem_ctx, + "(&(objectClass=crossRef)(netbiosName=%s)(systemFlags:%s:=%u))", + ldb_binary_encode_string(mem_ctx, domain), + LDB_OID_COMPARATOR_AND, + SYSTEM_FLAG_CR_NTDS_DOMAIN); + W_ERROR_HAVE_NO_MEMORY(domain_filter); + if (account) { + result_filter = talloc_asprintf(mem_ctx, "(sAMAccountName=%s)", + ldb_binary_encode_string(mem_ctx, account)); + W_ERROR_HAVE_NO_MEMORY(result_filter); + } + + talloc_free(domain); + break; + } + + /* A LDAP DN as a string */ + case DRSUAPI_DS_NAME_FORMAT_FQDN_1779: { + domain_filter = NULL; + name_dn = ldb_dn_new(mem_ctx, sam_ctx, name); + if (! ldb_dn_validate(name_dn)) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + break; + } + + /* A GUID as a string */ + case DRSUAPI_DS_NAME_FORMAT_GUID: { + struct GUID guid; + char *ldap_guid; + NTSTATUS nt_status; + domain_filter = NULL; + + nt_status = GUID_from_string(name, &guid); + if (!NT_STATUS_IS_OK(nt_status)) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + + ldap_guid = ldap_encode_ndr_GUID(mem_ctx, &guid); + if (!ldap_guid) { + return WERR_NOT_ENOUGH_MEMORY; + } + result_filter = talloc_asprintf(mem_ctx, "(objectGUID=%s)", + ldap_guid); + W_ERROR_HAVE_NO_MEMORY(result_filter); + break; + } + case DRSUAPI_DS_NAME_FORMAT_DISPLAY: { + domain_filter = NULL; + + result_filter = talloc_asprintf(mem_ctx, "(|(displayName=%s)(samAccountName=%s))", + ldb_binary_encode_string(mem_ctx, name), + ldb_binary_encode_string(mem_ctx, name)); + W_ERROR_HAVE_NO_MEMORY(result_filter); + break; + } + + /* A S-1234-5678 style string */ + case DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY: { + struct dom_sid *sid = dom_sid_parse_talloc(mem_ctx, name); + char *ldap_sid; + + domain_filter = NULL; + if (!sid) { + info1->dns_domain_name = NULL; + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + ldap_sid = ldap_encode_ndr_dom_sid(mem_ctx, + sid); + if (!ldap_sid) { + return WERR_NOT_ENOUGH_MEMORY; + } + result_filter = talloc_asprintf(mem_ctx, "(objectSid=%s)", + ldap_sid); + W_ERROR_HAVE_NO_MEMORY(result_filter); + break; + } + case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL: { + krb5_principal principal; + char *unparsed_name; + + ret = smb_krb5_init_context(mem_ctx, + (struct loadparm_context *)ldb_get_opaque(sam_ctx, "loadparm"), + &smb_krb5_context); + + if (ret) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Ensure we reject complete junk first */ + ret = krb5_parse_name(smb_krb5_context->krb5_context, name, &principal); + if (ret) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + + domain_filter = NULL; + + /* + * By getting the unparsed name here, we ensure the + * escaping is removed correctly (and trust the client + * less). The important thing here is that a + * userPrincipalName may have a space in it, and this + * must not be kerberos escaped to match this filter, + * so we specify KRB5_PRINCIPAL_UNPARSE_DISPLAY + */ + ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, + principal, + KRB5_PRINCIPAL_UNPARSE_DISPLAY, + &unparsed_name); + if (ret) { + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return WERR_NOT_ENOUGH_MEMORY; + } + + krb5_free_principal(smb_krb5_context->krb5_context, principal); + + /* The ldb_binary_encode_string() here avoid LDAP filter injection attacks */ + result_filter = talloc_asprintf(mem_ctx, "(&(userPrincipalName=%s)(objectClass=user))", + ldb_binary_encode_string(mem_ctx, unparsed_name)); + + free(unparsed_name); + W_ERROR_HAVE_NO_MEMORY(result_filter); + break; + } + case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: { + krb5_principal principal; + char *unparsed_name_short; + const krb5_data *component; + char *service; + + ret = smb_krb5_init_context(mem_ctx, + (struct loadparm_context *)ldb_get_opaque(sam_ctx, "loadparm"), + &smb_krb5_context); + + if (ret) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = krb5_parse_name(smb_krb5_context->krb5_context, name, &principal); + if (ret == 0 && + krb5_princ_size(smb_krb5_context->krb5_context, + principal) < 2) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return WERR_OK; + } else if (ret == 0) { + krb5_free_principal(smb_krb5_context->krb5_context, principal); + } + ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name, + KRB5_PRINCIPAL_PARSE_NO_REALM, &principal); + if (ret) { + return dns_domain_from_principal(mem_ctx, smb_krb5_context, + name, info1); + } + + domain_filter = NULL; + + ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &unparsed_name_short); + if (ret) { + krb5_free_principal(smb_krb5_context->krb5_context, principal); + return WERR_NOT_ENOUGH_MEMORY; + } + + component = krb5_princ_component(smb_krb5_context->krb5_context, + principal, 0); + service = (char *)component->data; + if ((krb5_princ_size(smb_krb5_context->krb5_context, + principal) == 2) && + (strcasecmp(service, "host") == 0)) { + /* the 'cn' attribute is just the leading part of the name */ + char *computer_name; + component = krb5_princ_component( + smb_krb5_context->krb5_context, + principal, 1); + computer_name = talloc_strndup(mem_ctx, (char *)component->data, + strcspn((char *)component->data, ".")); + if (computer_name == NULL) { + krb5_free_principal(smb_krb5_context->krb5_context, principal); + free(unparsed_name_short); + return WERR_NOT_ENOUGH_MEMORY; + } + + result_filter = talloc_asprintf(mem_ctx, "(|(&(servicePrincipalName=%s)(objectClass=user))(&(cn=%s)(objectClass=computer)))", + ldb_binary_encode_string(mem_ctx, unparsed_name_short), + ldb_binary_encode_string(mem_ctx, computer_name)); + } else { + result_filter = talloc_asprintf(mem_ctx, "(&(servicePrincipalName=%s)(objectClass=user))", + ldb_binary_encode_string(mem_ctx, unparsed_name_short)); + } + krb5_free_principal(smb_krb5_context->krb5_context, principal); + free(unparsed_name_short); + W_ERROR_HAVE_NO_MEMORY(result_filter); + + break; + } + default: { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + } + + if (format_flags & DRSUAPI_DS_NAME_FLAG_SYNTACTICAL_ONLY) { + return DsCrackNameOneSyntactical(mem_ctx, format_offered, format_desired, + name_dn, name, info1); + } + + return DsCrackNameOneFilter(sam_ctx, mem_ctx, + smb_krb5_context, + format_flags, format_offered, format_desired, + name_dn, name, + domain_filter, result_filter, + info1, scope, search_dn); +} + +/* Subcase of CrackNames. It is possible to translate a LDAP-style DN + * (FQDN_1779) into a canoical name without actually searching the + * database */ + +static WERROR DsCrackNameOneSyntactical(TALLOC_CTX *mem_ctx, + enum drsuapi_DsNameFormat format_offered, + enum drsuapi_DsNameFormat format_desired, + struct ldb_dn *name_dn, const char *name, + struct drsuapi_DsNameInfo1 *info1) +{ + char *cracked; + if (format_offered != DRSUAPI_DS_NAME_FORMAT_FQDN_1779) { + info1->status = DRSUAPI_DS_NAME_STATUS_NO_SYNTACTICAL_MAPPING; + return WERR_OK; + } + + switch (format_desired) { + case DRSUAPI_DS_NAME_FORMAT_CANONICAL: + cracked = ldb_dn_canonical_string(mem_ctx, name_dn); + break; + case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX: + cracked = ldb_dn_canonical_ex_string(mem_ctx, name_dn); + break; + default: + info1->status = DRSUAPI_DS_NAME_STATUS_NO_SYNTACTICAL_MAPPING; + return WERR_OK; + } + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + info1->result_name = cracked; + if (!cracked) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return WERR_OK; +} + +/* Given a filter for the domain, and one for the result, perform the + * ldb search. The format offered and desired flags change the + * behaviours, including what attributes to return. + * + * The smb_krb5_context is required because we use the krb5 libs for principal parsing + */ + +static WERROR DsCrackNameOneFilter(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + uint32_t format_flags, enum drsuapi_DsNameFormat format_offered, + enum drsuapi_DsNameFormat format_desired, + struct ldb_dn *name_dn, const char *name, + const char *domain_filter, const char *result_filter, + struct drsuapi_DsNameInfo1 *info1, + int scope, struct ldb_dn *search_dn) +{ + int ldb_ret; + struct ldb_result *domain_res = NULL; + const char * const *domain_attrs; + const char * const *result_attrs; + struct ldb_message **result_res = NULL; + struct ldb_message *result = NULL; + int i; + char *p; + struct ldb_dn *partitions_basedn = samdb_partitions_dn(sam_ctx, mem_ctx); + + const char * const _domain_attrs_1779[] = { "ncName", "dnsRoot", NULL}; + const char * const _result_attrs_null[] = { NULL }; + + const char * const _domain_attrs_canonical[] = { "ncName", "dnsRoot", NULL}; + const char * const _result_attrs_canonical[] = { "canonicalName", NULL }; + + const char * const _domain_attrs_nt4[] = { "ncName", "dnsRoot", "nETBIOSName", NULL}; + const char * const _result_attrs_nt4[] = { "sAMAccountName", "objectSid", "objectClass", NULL}; + + const char * const _domain_attrs_guid[] = { "ncName", "dnsRoot", NULL}; + const char * const _result_attrs_guid[] = { "objectGUID", NULL}; + + const char * const _domain_attrs_upn[] = { "ncName", "dnsRoot", NULL}; + const char * const _result_attrs_upn[] = { "userPrincipalName", NULL}; + + const char * const _domain_attrs_spn[] = { "ncName", "dnsRoot", NULL}; + const char * const _result_attrs_spn[] = { "servicePrincipalName", NULL}; + + const char * const _domain_attrs_display[] = { "ncName", "dnsRoot", NULL}; + const char * const _result_attrs_display[] = { "displayName", "samAccountName", NULL}; + + const char * const _domain_attrs_sid[] = { "ncName", "dnsRoot", NULL}; + const char * const _result_attrs_sid[] = { "objectSid", NULL}; + + const char * const _domain_attrs_none[] = { "ncName", "dnsRoot" , NULL}; + const char * const _result_attrs_none[] = { NULL}; + + /* here we need to set the attrs lists for domain and result lookups */ + switch (format_desired) { + case DRSUAPI_DS_NAME_FORMAT_FQDN_1779: + case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX: + domain_attrs = _domain_attrs_1779; + result_attrs = _result_attrs_null; + break; + case DRSUAPI_DS_NAME_FORMAT_CANONICAL: + domain_attrs = _domain_attrs_canonical; + result_attrs = _result_attrs_canonical; + break; + case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT: + domain_attrs = _domain_attrs_nt4; + result_attrs = _result_attrs_nt4; + break; + case DRSUAPI_DS_NAME_FORMAT_GUID: + domain_attrs = _domain_attrs_guid; + result_attrs = _result_attrs_guid; + break; + case DRSUAPI_DS_NAME_FORMAT_DISPLAY: + domain_attrs = _domain_attrs_display; + result_attrs = _result_attrs_display; + break; + case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL: + domain_attrs = _domain_attrs_upn; + result_attrs = _result_attrs_upn; + break; + case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: + domain_attrs = _domain_attrs_spn; + result_attrs = _result_attrs_spn; + break; + case DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY: + domain_attrs = _domain_attrs_sid; + result_attrs = _result_attrs_sid; + break; + default: + domain_attrs = _domain_attrs_none; + result_attrs = _result_attrs_none; + break; + } + + if (domain_filter) { + /* if we have a domain_filter look it up and set the result_basedn and the dns_domain_name */ + ldb_ret = ldb_search(sam_ctx, mem_ctx, &domain_res, + partitions_basedn, + LDB_SCOPE_ONELEVEL, + domain_attrs, + "%s", domain_filter); + + if (ldb_ret != LDB_SUCCESS) { + DEBUG(2, ("DsCrackNameOneFilter domain ref search failed: %s\n", ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + + switch (domain_res->count) { + case 1: + break; + case 0: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + default: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE; + return WERR_OK; + } + + info1->dns_domain_name = ldb_msg_find_attr_as_string(domain_res->msgs[0], "dnsRoot", NULL); + W_ERROR_HAVE_NO_MEMORY(info1->dns_domain_name); + info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY; + } else { + info1->dns_domain_name = NULL; + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + } + + if (result_filter) { + int ret; + struct ldb_result *res; + uint32_t dsdb_flags = 0; + struct ldb_dn *real_search_dn = NULL; + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + + /* + * From 4.1.4.2.11 of MS-DRSR + * if DS_NAME_FLAG_GCVERIFY in flags then + * rt := select all O from all + * where attrValue in GetAttrVals(O, att, false) + * else + * rt := select all O from subtree DefaultNC() + * where attrValue in GetAttrVals(O, att, false) + * endif + * return rt + */ + if (format_flags & DRSUAPI_DS_NAME_FLAG_GCVERIFY || + format_offered == DRSUAPI_DS_NAME_FORMAT_GUID) + { + dsdb_flags = DSDB_SEARCH_SEARCH_ALL_PARTITIONS; + } else if (domain_res) { + if (!search_dn) { + struct ldb_dn *tmp_dn = samdb_result_dn(sam_ctx, mem_ctx, domain_res->msgs[0], "ncName", NULL); + real_search_dn = tmp_dn; + } else { + real_search_dn = search_dn; + } + } else { + real_search_dn = ldb_get_default_basedn(sam_ctx); + } + if (format_offered == DRSUAPI_DS_NAME_FORMAT_GUID){ + dsdb_flags |= DSDB_SEARCH_SHOW_RECYCLED; + } + /* search with the 'phantom root' flag */ + ret = dsdb_search(sam_ctx, mem_ctx, &res, + real_search_dn, + scope, + result_attrs, + dsdb_flags, + "%s", result_filter); + if (ret != LDB_SUCCESS) { + DEBUG(2, ("DsCrackNameOneFilter search from '%s' with flags 0x%08x failed: %s\n", + ldb_dn_get_linearized(real_search_dn), + dsdb_flags, + ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + + ldb_ret = res->count; + result_res = res->msgs; + } else if (format_offered == DRSUAPI_DS_NAME_FORMAT_FQDN_1779) { + ldb_ret = gendb_search_dn(sam_ctx, mem_ctx, name_dn, &result_res, + result_attrs); + } else if (domain_res) { + name_dn = samdb_result_dn(sam_ctx, mem_ctx, domain_res->msgs[0], "ncName", NULL); + ldb_ret = gendb_search_dn(sam_ctx, mem_ctx, name_dn, &result_res, + result_attrs); + } else { + /* Can't happen */ + DEBUG(0, ("LOGIC ERROR: DsCrackNameOneFilter domain ref search not available: This can't happen...\n")); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + + switch (ldb_ret) { + case 1: + result = result_res[0]; + break; + case 0: + switch (format_offered) { + case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: + return DsCrackNameSPNAlias(sam_ctx, mem_ctx, + smb_krb5_context, + format_flags, format_offered, format_desired, + name, info1); + + case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL: + return DsCrackNameUPN(sam_ctx, mem_ctx, smb_krb5_context, + format_flags, format_offered, format_desired, + name, info1); + default: + break; + } + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + case -1: + DEBUG(2, ("DsCrackNameOneFilter result search failed: %s\n", ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + default: + switch (format_offered) { + case DRSUAPI_DS_NAME_FORMAT_CANONICAL: + case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX: + { + const char *canonical_name = NULL; /* Not required, but we get warnings... */ + /* We may need to manually filter further */ + for (i = 0; i < ldb_ret; i++) { + switch (format_offered) { + case DRSUAPI_DS_NAME_FORMAT_CANONICAL: + canonical_name = ldb_dn_canonical_string(mem_ctx, result_res[i]->dn); + break; + case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX: + canonical_name = ldb_dn_canonical_ex_string(mem_ctx, result_res[i]->dn); + break; + default: + break; + } + if (strcasecmp_m(canonical_name, name) == 0) { + result = result_res[i]; + break; + } + } + if (!result) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } + } + FALL_THROUGH; + default: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE; + return WERR_OK; + } + } + + info1->dns_domain_name = ldb_dn_canonical_string(mem_ctx, result->dn); + W_ERROR_HAVE_NO_MEMORY(info1->dns_domain_name); + p = strchr(info1->dns_domain_name, '/'); + if (p) { + p[0] = '\0'; + } + + /* here we can use result and domain_res[0] */ + switch (format_desired) { + case DRSUAPI_DS_NAME_FORMAT_FQDN_1779: { + info1->result_name = ldb_dn_alloc_linearized(mem_ctx, result->dn); + W_ERROR_HAVE_NO_MEMORY(info1->result_name); + + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + return WERR_OK; + } + case DRSUAPI_DS_NAME_FORMAT_CANONICAL: { + info1->result_name = ldb_msg_find_attr_as_string(result, "canonicalName", NULL); + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + return WERR_OK; + } + case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX: { + /* Not in the virtual ldb attribute */ + return DsCrackNameOneSyntactical(mem_ctx, + DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX, + result->dn, name, info1); + } + case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT: { + + const struct dom_sid *sid = samdb_result_dom_sid(mem_ctx, result, "objectSid"); + const char *_acc = "", *_dom = ""; + if (sid == NULL) { + info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING; + return WERR_OK; + } + + if (samdb_find_attribute(sam_ctx, result, "objectClass", + "domain")) { + /* This can also find a DomainDNSZones entry, + * but it won't have the SID we just + * checked. */ + ldb_ret = ldb_search(sam_ctx, mem_ctx, &domain_res, + partitions_basedn, + LDB_SCOPE_ONELEVEL, + domain_attrs, + "(ncName=%s)", ldb_dn_get_linearized(result->dn)); + + if (ldb_ret != LDB_SUCCESS) { + DEBUG(2, ("DsCrackNameOneFilter domain ref search failed: %s\n", ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + + switch (domain_res->count) { + case 1: + break; + case 0: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + default: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE; + return WERR_OK; + } + _dom = ldb_msg_find_attr_as_string(domain_res->msgs[0], "nETBIOSName", NULL); + W_ERROR_HAVE_NO_MEMORY(_dom); + } else { + _acc = ldb_msg_find_attr_as_string(result, "sAMAccountName", NULL); + if (!_acc) { + info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING; + return WERR_OK; + } + if (dom_sid_in_domain(dom_sid_parse_talloc(mem_ctx, SID_BUILTIN), sid)) { + _dom = "BUILTIN"; + } else { + const char *attrs[] = { NULL }; + struct ldb_result *domain_res2; + struct dom_sid *dom_sid = dom_sid_dup(mem_ctx, sid); + if (!dom_sid) { + return WERR_OK; + } + dom_sid->num_auths--; + ldb_ret = ldb_search(sam_ctx, mem_ctx, &domain_res, + NULL, + LDB_SCOPE_BASE, + attrs, + "(&(objectSid=%s)(objectClass=domain))", + ldap_encode_ndr_dom_sid(mem_ctx, dom_sid)); + + if (ldb_ret != LDB_SUCCESS) { + DEBUG(2, ("DsCrackNameOneFilter domain search failed: %s\n", ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + + switch (domain_res->count) { + case 1: + break; + case 0: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + default: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE; + return WERR_OK; + } + + ldb_ret = ldb_search(sam_ctx, mem_ctx, &domain_res2, + partitions_basedn, + LDB_SCOPE_ONELEVEL, + domain_attrs, + "(ncName=%s)", ldb_dn_get_linearized(domain_res->msgs[0]->dn)); + + if (ldb_ret != LDB_SUCCESS) { + DEBUG(2, ("DsCrackNameOneFilter domain ref search failed: %s\n", ldb_errstring(sam_ctx))); + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + + switch (domain_res2->count) { + case 1: + break; + case 0: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + default: + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE; + return WERR_OK; + } + _dom = ldb_msg_find_attr_as_string(domain_res2->msgs[0], "nETBIOSName", NULL); + W_ERROR_HAVE_NO_MEMORY(_dom); + } + } + + info1->result_name = talloc_asprintf(mem_ctx, "%s\\%s", _dom, _acc); + W_ERROR_HAVE_NO_MEMORY(info1->result_name); + + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + return WERR_OK; + } + case DRSUAPI_DS_NAME_FORMAT_GUID: { + struct GUID guid; + + guid = samdb_result_guid(result, "objectGUID"); + + info1->result_name = GUID_string2(mem_ctx, &guid); + W_ERROR_HAVE_NO_MEMORY(info1->result_name); + + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + return WERR_OK; + } + case DRSUAPI_DS_NAME_FORMAT_DISPLAY: { + info1->result_name = ldb_msg_find_attr_as_string(result, "displayName", NULL); + if (!info1->result_name) { + info1->result_name = ldb_msg_find_attr_as_string(result, "sAMAccountName", NULL); + } + if (!info1->result_name) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + } else { + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + } + return WERR_OK; + } + case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: { + struct ldb_message_element *el + = ldb_msg_find_element(result, + "servicePrincipalName"); + if (el == NULL) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + return WERR_OK; + } else if (el->num_values > 1) { + info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE; + return WERR_OK; + } + + info1->result_name = ldb_msg_find_attr_as_string(result, "servicePrincipalName", NULL); + if (!info1->result_name) { + info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING; + } else { + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + } + return WERR_OK; + } + case DRSUAPI_DS_NAME_FORMAT_DNS_DOMAIN: { + info1->dns_domain_name = NULL; + info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR; + return WERR_OK; + } + case DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY: { + const struct dom_sid *sid = samdb_result_dom_sid(mem_ctx, result, "objectSid"); + + if (sid == NULL) { + info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING; + return WERR_OK; + } + + info1->result_name = dom_sid_string(mem_ctx, sid); + W_ERROR_HAVE_NO_MEMORY(info1->result_name); + + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + return WERR_OK; + } + case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL: { + info1->result_name = ldb_msg_find_attr_as_string(result, "userPrincipalName", NULL); + if (!info1->result_name) { + info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING; + } else { + info1->status = DRSUAPI_DS_NAME_STATUS_OK; + } + return WERR_OK; + } + default: + info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING; + return WERR_OK; + } +} + +/* Given a user Principal Name (such as foo@bar.com), + * return the user and domain DNs. This is used in the KDC to then + * return the Keys and evaluate policy */ + +NTSTATUS crack_user_principal_name(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + const char *user_principal_name, + struct ldb_dn **user_dn, + struct ldb_dn **domain_dn) +{ + WERROR werr; + struct drsuapi_DsNameInfo1 info1; + werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0, + DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + user_principal_name, + &info1); + if (!W_ERROR_IS_OK(werr)) { + return werror_to_ntstatus(werr); + } + switch (info1.status) { + case DRSUAPI_DS_NAME_STATUS_OK: + break; + case DRSUAPI_DS_NAME_STATUS_NOT_FOUND: + case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY: + case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE: + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR: + default: + return NT_STATUS_UNSUCCESSFUL; + } + + *user_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name); + + if (domain_dn) { + werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0, + DRSUAPI_DS_NAME_FORMAT_CANONICAL, + DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + talloc_asprintf(mem_ctx, "%s/", + info1.dns_domain_name), + &info1); + if (!W_ERROR_IS_OK(werr)) { + return werror_to_ntstatus(werr); + } + switch (info1.status) { + case DRSUAPI_DS_NAME_STATUS_OK: + break; + case DRSUAPI_DS_NAME_STATUS_NOT_FOUND: + case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY: + case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE: + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR: + default: + return NT_STATUS_UNSUCCESSFUL; + } + + *domain_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name); + } + + return NT_STATUS_OK; +} + +/* Given a Service Principal Name (such as host/foo.bar.com@BAR.COM), + * return the user and domain DNs. This is used in the KDC to then + * return the Keys and evaluate policy */ + +NTSTATUS crack_service_principal_name(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + const char *service_principal_name, + struct ldb_dn **user_dn, + struct ldb_dn **domain_dn) +{ + WERROR werr; + struct drsuapi_DsNameInfo1 info1; + werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0, + DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + service_principal_name, + &info1); + if (!W_ERROR_IS_OK(werr)) { + return werror_to_ntstatus(werr); + } + switch (info1.status) { + case DRSUAPI_DS_NAME_STATUS_OK: + break; + case DRSUAPI_DS_NAME_STATUS_NOT_FOUND: + case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY: + case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE: + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR: + default: + return NT_STATUS_UNSUCCESSFUL; + } + + *user_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name); + + if (domain_dn) { + werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0, + DRSUAPI_DS_NAME_FORMAT_CANONICAL, + DRSUAPI_DS_NAME_FORMAT_FQDN_1779, + talloc_asprintf(mem_ctx, "%s/", + info1.dns_domain_name), + &info1); + if (!W_ERROR_IS_OK(werr)) { + return werror_to_ntstatus(werr); + } + switch (info1.status) { + case DRSUAPI_DS_NAME_STATUS_OK: + break; + case DRSUAPI_DS_NAME_STATUS_NOT_FOUND: + case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY: + case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE: + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR: + default: + return NT_STATUS_UNSUCCESSFUL; + } + + *domain_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name); + } + + return NT_STATUS_OK; +} + +NTSTATUS crack_name_to_nt4_name(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + enum drsuapi_DsNameFormat format_offered, + const char *name, + const char **nt4_domain, const char **nt4_account) +{ + WERROR werr; + struct drsuapi_DsNameInfo1 info1; + char *p; + + /* Handle anonymous bind */ + if (!name || !*name) { + *nt4_domain = ""; + *nt4_account = ""; + return NT_STATUS_OK; + } + + werr = DsCrackNameOneName(ldb, mem_ctx, 0, + format_offered, + DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + name, + &info1); + if (!W_ERROR_IS_OK(werr)) { + return werror_to_ntstatus(werr); + } + switch (info1.status) { + case DRSUAPI_DS_NAME_STATUS_OK: + break; + case DRSUAPI_DS_NAME_STATUS_NOT_FOUND: + case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY: + case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE: + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR: + default: + return NT_STATUS_UNSUCCESSFUL; + } + + *nt4_domain = talloc_strdup(mem_ctx, info1.result_name); + if (*nt4_domain == NULL) { + return NT_STATUS_NO_MEMORY; + } + + p = strchr(*nt4_domain, '\\'); + if (!p) { + return NT_STATUS_INVALID_PARAMETER; + } + p[0] = '\0'; + + *nt4_account = talloc_strdup(mem_ctx, &p[1]); + if (*nt4_account == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +NTSTATUS crack_auto_name_to_nt4_name(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *name, + const char **nt4_domain, + const char **nt4_account) +{ + enum drsuapi_DsNameFormat format_offered = DRSUAPI_DS_NAME_FORMAT_UNKNOWN; + + /* Handle anonymous bind */ + if (!name || !*name) { + *nt4_domain = ""; + *nt4_account = ""; + return NT_STATUS_OK; + } + + /* + * Here we only consider a subset of the possible name forms listed in + * [MS-ADTS] 5.1.1.1.1, and we don't retry with a different name form if + * the first attempt fails. + */ + + if (strchr_m(name, '=')) { + format_offered = DRSUAPI_DS_NAME_FORMAT_FQDN_1779; + } else if (strchr_m(name, '@')) { + format_offered = DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL; + } else if (strchr_m(name, '\\')) { + format_offered = DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT; + } else if (strchr_m(name, '\n')) { + format_offered = DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX; + } else if (strchr_m(name, '/')) { + format_offered = DRSUAPI_DS_NAME_FORMAT_CANONICAL; + } else if ((name[0] == 'S' || name[0] == 's') && name[1] == '-') { + format_offered = DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY; + } else { + return NT_STATUS_NO_SUCH_USER; + } + + return crack_name_to_nt4_name(mem_ctx, ldb, format_offered, name, nt4_domain, nt4_account); +} + + +WERROR dcesrv_drsuapi_ListRoles(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + const struct drsuapi_DsNameRequest1 *req1, + struct drsuapi_DsNameCtr1 **ctr1) +{ + struct drsuapi_DsNameInfo1 *names; + uint32_t i; + uint32_t count = 5;/*number of fsmo role owners we are going to return*/ + + *ctr1 = talloc(mem_ctx, struct drsuapi_DsNameCtr1); + W_ERROR_HAVE_NO_MEMORY(*ctr1); + names = talloc_array(mem_ctx, struct drsuapi_DsNameInfo1, count); + W_ERROR_HAVE_NO_MEMORY(names); + + for (i = 0; i < count; i++) { + WERROR werr; + struct ldb_dn *role_owner_dn, *fsmo_role_dn, *server_dn; + werr = dsdb_get_fsmo_role_info(mem_ctx, sam_ctx, i, + &fsmo_role_dn, &role_owner_dn); + if(!W_ERROR_IS_OK(werr)) { + return werr; + } + server_dn = ldb_dn_copy(mem_ctx, role_owner_dn); + ldb_dn_remove_child_components(server_dn, 1); + names[i].status = DRSUAPI_DS_NAME_STATUS_OK; + names[i].dns_domain_name = samdb_dn_to_dnshostname(sam_ctx, mem_ctx, + server_dn); + if(!names[i].dns_domain_name) { + DEBUG(4, ("list_roles: Failed to find dNSHostName for server %s\n", + ldb_dn_get_linearized(server_dn))); + } + names[i].result_name = talloc_strdup(mem_ctx, ldb_dn_get_linearized(role_owner_dn)); + } + + (*ctr1)->count = count; + (*ctr1)->array = names; + + return WERR_OK; +} + +WERROR dcesrv_drsuapi_CrackNamesByNameFormat(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + const struct drsuapi_DsNameRequest1 *req1, + struct drsuapi_DsNameCtr1 **ctr1) +{ + struct drsuapi_DsNameInfo1 *names; + uint32_t i, count; + WERROR status; + + *ctr1 = talloc_zero(mem_ctx, struct drsuapi_DsNameCtr1); + W_ERROR_HAVE_NO_MEMORY(*ctr1); + + count = req1->count; + names = talloc_array(mem_ctx, struct drsuapi_DsNameInfo1, count); + W_ERROR_HAVE_NO_MEMORY(names); + + for (i=0; i < count; i++) { + status = DsCrackNameOneName(sam_ctx, mem_ctx, + req1->format_flags, + req1->format_offered, + req1->format_desired, + req1->names[i].str, + &names[i]); + if (!W_ERROR_IS_OK(status)) { + return status; + } + } + + (*ctr1)->count = count; + (*ctr1)->array = names; + + return WERR_OK; +} + +WERROR dcesrv_drsuapi_ListInfoServer(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + const struct drsuapi_DsNameRequest1 *req1, + struct drsuapi_DsNameCtr1 **_ctr1) +{ + struct drsuapi_DsNameInfo1 *names; + struct ldb_result *res; + struct ldb_dn *server_dn, *dn; + struct drsuapi_DsNameCtr1 *ctr1; + int ret, i; + const char *str; + const char *attrs[] = { + "dn", + "dNSHostName", + "serverReference", + NULL + }; + + *_ctr1 = NULL; + + ctr1 = talloc_zero(mem_ctx, struct drsuapi_DsNameCtr1); + W_ERROR_HAVE_NO_MEMORY(ctr1); + + /* + * No magic value here, we have to return 3 entries according to the + * MS-DRSR.pdf + */ + ctr1->count = 3; + names = talloc_zero_array(ctr1, struct drsuapi_DsNameInfo1, + ctr1->count); + W_ERROR_HAVE_NO_MEMORY(names); + ctr1->array = names; + + for (i=0; i < ctr1->count; i++) { + names[i].status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND; + } + *_ctr1 = ctr1; + + if (req1->count != 1) { + DEBUG(1, ("Expected a count of 1 for the ListInfoServer crackname \n")); + return WERR_OK; + } + + if (req1->names[0].str == NULL) { + return WERR_OK; + } + + server_dn = ldb_dn_new(mem_ctx, sam_ctx, req1->names[0].str); + W_ERROR_HAVE_NO_MEMORY(server_dn); + + ret = ldb_search(sam_ctx, mem_ctx, &res, server_dn, LDB_SCOPE_ONELEVEL, + NULL, "(objectClass=nTDSDSA)"); + + if (ret != LDB_SUCCESS) { + DEBUG(1, ("Search for objectClass=nTDSDSA " + "returned less than 1 objects\n")); + return WERR_OK; + } + + if (res->count != 1) { + DEBUG(1, ("Search for objectClass=nTDSDSA " + "returned less than 1 objects\n")); + return WERR_OK; + } + + if (res->msgs[0]->dn) { + names[0].result_name = ldb_dn_alloc_linearized(names, res->msgs[0]->dn); + W_ERROR_HAVE_NO_MEMORY(names[0].result_name); + names[0].status = DRSUAPI_DS_NAME_STATUS_OK; + } + + talloc_free(res); + + ret = ldb_search(sam_ctx, mem_ctx, &res, server_dn, LDB_SCOPE_BASE, + attrs, "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("Search for objectClass=* on dn %s" + "returned %s\n", req1->names[0].str, + ldb_strerror(ret))); + return WERR_OK; + } + + if (res->count != 1) { + DEBUG(1, ("Search for objectClass=* on dn %s" + "returned less than 1 objects\n", req1->names[0].str)); + return WERR_OK; + } + + str = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL); + if (str != NULL) { + names[1].result_name = talloc_strdup(names, str); + W_ERROR_HAVE_NO_MEMORY(names[1].result_name); + names[1].status = DRSUAPI_DS_NAME_STATUS_OK; + } + + dn = ldb_msg_find_attr_as_dn(sam_ctx, mem_ctx, res->msgs[0], "serverReference"); + if (dn != NULL) { + names[2].result_name = ldb_dn_alloc_linearized(names, dn); + W_ERROR_HAVE_NO_MEMORY(names[2].result_name); + names[2].status = DRSUAPI_DS_NAME_STATUS_OK; + } + + talloc_free(dn); + talloc_free(res); + + return WERR_OK; +} |