From 71f3a25ff7ff5866c77f580daa4814ca985167ce Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Thu, 29 Sep 2011 17:44:28 +1000 Subject: s4-auth: rework map_user_info() to use cracknames to properly support multi-domain forests we need to determine if an incoming username is part of a known forest domain or not. To do this for all possible SPN forms, we need to use CrackNames. This changes map_user_info() to use CrackNames if a SAM context is available, and asks the CrackNames services to parse the incoming username and domain into a NT4 form, which can then be used in the SAM. Pair-Programmed-With: Andrew Bartlett --- source4/auth/ntlm/auth.c | 2 +- source4/auth/ntlm/auth_util.c | 226 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 215 insertions(+), 13 deletions(-) (limited to 'source4/auth/ntlm') diff --git a/source4/auth/ntlm/auth.c b/source4/auth/ntlm/auth.c index 74e97cfd7d..69cbff6e9a 100644 --- a/source4/auth/ntlm/auth.c +++ b/source4/auth/ntlm/auth.c @@ -251,7 +251,7 @@ _PUBLIC_ struct tevent_req *auth_check_password_send(TALLOC_CTX *mem_ctx, state->user_info = user_info; if (!user_info->mapped_state) { - nt_status = map_user_info(req, lpcfg_workgroup(auth_ctx->lp_ctx), + nt_status = map_user_info(auth_ctx->sam_ctx, req, lpcfg_workgroup(auth_ctx->lp_ctx), user_info, &user_info_tmp); if (tevent_req_nterror(req, nt_status)) { return tevent_req_post(req, ev); diff --git a/source4/auth/ntlm/auth_util.c b/source4/auth/ntlm/auth_util.c index c19b5cfd42..16977fa00a 100644 --- a/source4/auth/ntlm/auth_util.c +++ b/source4/auth/ntlm/auth_util.c @@ -26,6 +26,8 @@ #include "libcli/auth/libcli_auth.h" #include "param/param.h" #include "auth/ntlm/auth_proto.h" +#include "librpc/gen_ndr/drsuapi.h" +#include "dsdb/samdb/samdb.h" /* this default function can be used by mostly all backends * which don't want to set a challenge @@ -39,54 +41,254 @@ NTSTATUS auth_get_challenge_not_implemented(struct auth_method_context *ctx, TAL /**************************************************************************** Create an auth_usersupplied_data structure after appropriate mapping. ****************************************************************************/ +static NTSTATUS map_user_info_cracknames(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + const char *default_domain, + const struct auth_usersupplied_info *user_info, + struct auth_usersupplied_info **user_info_mapped) +{ + char *domain; + char *account_name; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + WERROR werr; + struct drsuapi_DsNameInfo1 info1; + + DEBUG(5,("map_user_info_cracknames: Mapping user [%s]\\[%s] from workstation [%s]\n", + user_info->client.domain_name, user_info->client.account_name, user_info->workstation_name)); + + account_name = talloc_strdup(tmp_ctx, user_info->client.account_name); + if (!account_name) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + /* use cracknames to work out what domain is being + asked for */ + if (strchr_m(user_info->client.account_name, '@') != NULL) { + werr = DsCrackNameOneName(sam_ctx, tmp_ctx, 0, + DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + user_info->client.account_name, + &info1); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(2,("map_user_info: Failed cracknames of account '%s'\n", + user_info->client.account_name)); + talloc_free(tmp_ctx); + return werror_to_ntstatus(werr); + } + switch (info1.status) { + case DRSUAPI_DS_NAME_STATUS_OK: + break; + case DRSUAPI_DS_NAME_STATUS_NOT_FOUND: + DEBUG(2,("map_user_info: Cracknames of account '%s' -> NOT_FOUND\n", + user_info->client.account_name)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY: + DEBUG(2,("map_user_info: Cracknames of account '%s' -> DOMAIN_ONLY\n", + user_info->client.account_name)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE: + DEBUG(2,("map_user_info: Cracknames of account '%s' -> NOT_UNIQUE\n", + user_info->client.account_name)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR: + DEBUG(2,("map_user_info: Cracknames of account '%s' -> RESOLVE_ERROR\n", + user_info->client.account_name)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + default: + DEBUG(2,("map_user_info: Cracknames of account '%s' -> unknown error %u\n", + user_info->client.account_name, info1.status)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + } + /* info1.result_name is in DOMAIN\username + * form, which we need to split up into the + * user_info_mapped structure + */ + domain = talloc_strdup(tmp_ctx, info1.result_name); + if (domain == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + account_name = strchr_m(domain, '\\'); + if (account_name == NULL) { + DEBUG(2,("map_user_info: Cracknames of account '%s' gave invalid result '%s'\n", + user_info->client.account_name, info1.result_name)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + } + *account_name = 0; + account_name = talloc_strdup(tmp_ctx, account_name+1); + if (account_name == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + } else { + char *domain_name; + if (user_info->client.domain_name && *user_info->client.domain_name) { + domain_name = talloc_asprintf(tmp_ctx, "%s\\", user_info->client.domain_name); + } else { + domain_name = talloc_strdup(tmp_ctx, default_domain); + } + if (domain_name == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0, + DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT, + domain_name, + &info1); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(2,("map_user_info: Failed cracknames of domain '%s'\n", + domain_name)); + talloc_free(tmp_ctx); + return werror_to_ntstatus(werr); + } + + /* we use the account_name as-is, but get the + * domain name from cracknames if possible */ + account_name = talloc_strdup(mem_ctx, user_info->client.account_name); + if (account_name == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + switch (info1.status) { + case DRSUAPI_DS_NAME_STATUS_OK: + case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY: + domain = talloc_strdup(tmp_ctx, info1.result_name); + if (domain == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + if (domain[strlen_m(domain)-1] == '\\') { + domain[strlen_m(domain)-1] = 0; + } + break; + case DRSUAPI_DS_NAME_STATUS_NOT_FOUND: + /* the domain is unknown - use the + default domain */ + domain = talloc_strdup(tmp_ctx, default_domain); + break; + case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE: + DEBUG(2,("map_user_info: Cracknames of domain '%s' -> NOT_UNIQUE\n", + domain_name)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR: + DEBUG(2,("map_user_info: Cracknames of domain '%s' -> RESOLVE_ERROR\n", + domain_name)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + default: + DEBUG(2,("map_user_info: Cracknames of account '%s' -> unknown error %u\n", + domain_name, info1.status)); + talloc_free(tmp_ctx); + return NT_STATUS_NO_SUCH_USER; + } + /* domain and account_name are filled in above */ + } -NTSTATUS map_user_info(TALLOC_CTX *mem_ctx, + *user_info_mapped = talloc_zero(mem_ctx, struct auth_usersupplied_info); + if (!*user_info_mapped) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + if (!talloc_reference(*user_info_mapped, user_info)) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + **user_info_mapped = *user_info; + (*user_info_mapped)->mapped_state = true; + (*user_info_mapped)->mapped.domain_name = talloc_strdup(*user_info_mapped, domain); + (*user_info_mapped)->mapped.account_name = talloc_strdup(*user_info_mapped, account_name); + talloc_free(tmp_ctx); + if (!(*user_info_mapped)->mapped.domain_name + || !(*user_info_mapped)->mapped.account_name) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + + +/**************************************************************************** + Create an auth_usersupplied_data structure after appropriate mapping. +****************************************************************************/ +NTSTATUS map_user_info(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, const char *default_domain, const struct auth_usersupplied_info *user_info, struct auth_usersupplied_info **user_info_mapped) { - const char *domain; + char *domain; char *account_name; char *d; - DEBUG(5,("map_user_info: Mapping user [%s]\\[%s] from workstation [%s]\n", - user_info->client.domain_name, user_info->client.account_name, user_info->workstation_name)); + TALLOC_CTX *tmp_ctx; + + if (sam_ctx != NULL) { + /* if possible, use cracknames to parse the + domain/account */ + return map_user_info_cracknames(sam_ctx, mem_ctx, default_domain, user_info, user_info_mapped); + } + + DEBUG(0,("map_user_info: Mapping user [%s]\\[%s] from workstation [%s] default_domain=%s\n", + user_info->client.domain_name, user_info->client.account_name, user_info->workstation_name, + default_domain)); - account_name = talloc_strdup(mem_ctx, user_info->client.account_name); + tmp_ctx = talloc_new(mem_ctx); + + account_name = talloc_strdup(tmp_ctx, user_info->client.account_name); if (!account_name) { + talloc_free(tmp_ctx); return NT_STATUS_NO_MEMORY; } - /* don't allow "" as a domain, fixes a Win9X bug - where it doesn't supply a domain for logon script - 'net use' commands. */ + /* don't allow "" as a domain, fixes a Win9X bug where it + doesn't supply a domain for logon script 'net use' + commands. */ - /* Split user@realm names into user and realm components. This is TODO to fix with proper userprincipalname support */ + /* Split user@realm names into user and realm components. + * This is TODO to fix with proper userprincipalname + * support */ if (user_info->client.domain_name && *user_info->client.domain_name) { - domain = user_info->client.domain_name; + domain = talloc_strdup(tmp_ctx, user_info->client.domain_name); } else if (strchr_m(user_info->client.account_name, '@')) { d = strchr_m(account_name, '@'); if (!d) { + talloc_free(tmp_ctx); return NT_STATUS_INTERNAL_ERROR; } d[0] = '\0'; d++; domain = d; } else { - domain = default_domain; + domain = talloc_strdup(tmp_ctx, default_domain); } + if (domain == NULL) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } *user_info_mapped = talloc_zero(mem_ctx, struct auth_usersupplied_info); if (!*user_info_mapped) { + talloc_free(tmp_ctx); return NT_STATUS_NO_MEMORY; } if (!talloc_reference(*user_info_mapped, user_info)) { + talloc_free(tmp_ctx); return NT_STATUS_NO_MEMORY; } **user_info_mapped = *user_info; (*user_info_mapped)->mapped_state = true; (*user_info_mapped)->mapped.domain_name = talloc_strdup(*user_info_mapped, domain); (*user_info_mapped)->mapped.account_name = talloc_strdup(*user_info_mapped, account_name); - talloc_free(account_name); + talloc_free(tmp_ctx); if (!(*user_info_mapped)->mapped.domain_name || !(*user_info_mapped)->mapped.account_name) { return NT_STATUS_NO_MEMORY; -- cgit