From 869348dfcbf66a88998cb80c00902848db96901f Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 6 Apr 2004 16:44:24 +0000 Subject: r84: Implement --required-membership-of=, an ntlm_auth option that restricts all authentication to members of this particular group. Also implement an option to allow ntlm_auth to get 'squashed' error codes, which are safer to communicate to remote network clients. Andrew Bartlett (This used to be commit eb1c1b5eb086f49a230142ad2de45dc0e9691df3) --- source3/libsmb/nterr.c | 28 ++++++++ source3/nsswitch/winbindd_nss.h | 3 + source3/nsswitch/winbindd_pam.c | 119 +++++++++++++++++++++++++++++-- source3/utils/ntlm_auth.c | 151 ++++++++++++++++++++++++++++++---------- 4 files changed, 258 insertions(+), 43 deletions(-) (limited to 'source3') diff --git a/source3/libsmb/nterr.c b/source3/libsmb/nterr.c index 166229ec6c..b01451ea0f 100644 --- a/source3/libsmb/nterr.c +++ b/source3/libsmb/nterr.c @@ -717,3 +717,31 @@ NTSTATUS nt_status_string_to_code(char *nt_status_str) } return NT_STATUS_UNSUCCESSFUL; } + + +/** + * Squash an NT_STATUS in line with security requirements. + * In an attempt to avoid giving the whole game away when users + * are authenticating, NT replaces both NT_STATUS_NO_SUCH_USER and + * NT_STATUS_WRONG_PASSWORD with NT_STATUS_LOGON_FAILURE in certain situations + * (session setups in particular). + * + * @param nt_status NTSTATUS input for squashing. + * @return the 'squashed' nt_status + **/ + +NTSTATUS nt_status_squash(NTSTATUS nt_status) +{ + if NT_STATUS_IS_OK(nt_status) { + return nt_status; + } else if NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER) { + /* Match WinXP and don't give the game away */ + return NT_STATUS_LOGON_FAILURE; + + } else if NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD) { + /* Match WinXP and don't give the game away */ + return NT_STATUS_LOGON_FAILURE; + } else { + return nt_status; + } +} diff --git a/source3/nsswitch/winbindd_nss.h b/source3/nsswitch/winbindd_nss.h index d0c80412df..4ddce11951 100644 --- a/source3/nsswitch/winbindd_nss.h +++ b/source3/nsswitch/winbindd_nss.h @@ -157,6 +157,7 @@ typedef struct winbindd_gr { #define WBFLAG_ALLOCATE_RID 0x0040 #define WBFLAG_PAM_UNIX_NAME 0x0080 #define WBFLAG_PAM_AFS_TOKEN 0x0100 +#define WBFLAG_PAM_NT_STATUS_SQUASH 0x0200 /* Winbind request structure */ @@ -179,6 +180,7 @@ struct winbindd_request { character is. */ fstring user; fstring pass; + fstring required_membership_sid; } auth; /* pam_winbind auth module */ struct { unsigned char chal[8]; @@ -189,6 +191,7 @@ struct winbindd_request { fstring nt_resp; uint16 nt_resp_len; fstring workstation; + fstring required_membership_sid; } auth_crap; struct { fstring user; diff --git a/source3/nsswitch/winbindd_pam.c b/source3/nsswitch/winbindd_pam.c index f3e7daa6bf..a2ebe0ddbc 100644 --- a/source3/nsswitch/winbindd_pam.c +++ b/source3/nsswitch/winbindd_pam.c @@ -54,8 +54,98 @@ static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx, return NT_STATUS_OK; } +static NTSTATUS check_info3_in_group(TALLOC_CTX *mem_ctx, + NET_USER_INFO_3 *info3, + const char *group_sid) +{ + DOM_SID required_membership_sid; + DOM_SID *all_sids; + size_t num_all_sids = (2 + info3->num_groups2 + info3->num_other_sids); + size_t i, j = 0; + + /* Parse the 'required group' SID */ + + if (!group_sid || !group_sid[0]) { + /* NO sid supplied, all users may access */ + return NT_STATUS_OK; + } + + if (!string_to_sid(&required_membership_sid, group_sid)) { + DEBUG(0, ("winbindd_pam_auth: could not parse %s as a SID!", + group_sid)); + + return NT_STATUS_INVALID_PARAMETER; + } + + all_sids = talloc(mem_ctx, sizeof(DOM_SID) * num_all_sids); + if (!all_sids) + return NT_STATUS_NO_MEMORY; + + /* and create (by appending rids) the 'domain' sids */ + + sid_copy(&all_sids[0], &(info3->dom_sid.sid)); + + if (!sid_append_rid(&all_sids[0], info3->user_rid)) { + DEBUG(3,("could not append user's primary RID 0x%x\n", + info3->user_rid)); + + return NT_STATUS_INVALID_PARAMETER; + } + j++; + + sid_copy(&all_sids[1], &(info3->dom_sid.sid)); + + if (!sid_append_rid(&all_sids[1], info3->group_rid)) { + DEBUG(3,("could not append additional group rid 0x%x\n", + info3->group_rid)); + + return NT_STATUS_INVALID_PARAMETER; + } + j++; + + for (i = 0; i < info3->num_groups2; i++) { + + sid_copy(&all_sids[j], &(info3->dom_sid.sid)); + + if (!sid_append_rid(&all_sids[j], info3->gids[j].g_rid)) { + DEBUG(3,("could not append additional group rid 0x%x\n", + info3->gids[j].g_rid)); + + return NT_STATUS_INVALID_PARAMETER; + } + j++; + } + + /* Copy 'other' sids. We need to do sid filtering here to + prevent possible elevation of privileges. See: + + http://www.microsoft.com/windows2000/techinfo/administration/security/sidfilter.asp + */ + + for (i = 0; i < info3->num_other_sids; j++) { + sid_copy(&all_sids[info3->num_groups2 + i + 2], + &info3->other_sids[j].sid); + j++; + } + + for (i = 0; i < j; i++) { + fstring sid1, sid2; + DEBUG(10, ("User has SID: %s\n", + sid_to_string(sid1, &all_sids[i]))); + if (sid_equal(&required_membership_sid, &all_sids[i])) { + DEBUG(10, ("SID %s matches %s - user permitted to authenticate!\n", + sid_to_string(sid1, &required_membership_sid), sid_to_string(sid2, &all_sids[i]))); + return NT_STATUS_OK; + } + } + + /* Do not distinguish this error from a wrong username/pw */ + + return NT_STATUS_LOGON_FAILURE; +} + /********************************************************************** - Authenticate a user with a clear test password + Authenticate a user with a clear text password **********************************************************************/ enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state) @@ -125,7 +215,7 @@ enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state) } if (!(contact_domain = find_our_domain())) { - DEBUG(1, ("Authenticatoin for [%s] -> [%s]\\[%s] in our domain failed - we can't find our domain!\n", + DEBUG(1, ("Authentication for [%s] -> [%s]\\[%s] in our domain failed - we can't find our domain!\n", state->request.data.auth.user, name_domain, name_user)); result = NT_STATUS_NO_SUCH_USER; goto done; @@ -190,8 +280,16 @@ enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state) if (NT_STATUS_IS_OK(result)) { netsamlogon_cache_store( cli->mem_ctx, &info3 ); wcache_invalidate_samlogon(find_domain_from_name(name_domain), &info3); + + /* Check if the user is in the right group */ + + if (!NT_STATUS_IS_OK(result = check_info3_in_group(mem_ctx, &info3, state->request.data.auth.required_membership_sid))) { + DEBUG(3, ("User %s is not in the required group (%s), so plaintext authentication is rejected\n", + state->request.data.auth.user, + state->request.data.auth.required_membership_sid)); + } } - + 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)))) { @@ -355,7 +453,7 @@ enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state) if ( IS_DC ) { if (!(contact_domain = find_domain_from_name(name_domain))) { DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n", - state->request.data.auth.user, name_domain, name_user, name_domain)); + state->request.data.auth_crap.user, name_domain, name_user, name_domain)); result = NT_STATUS_NO_SUCH_USER; goto done; } @@ -369,7 +467,7 @@ enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state) if (!(contact_domain = find_our_domain())) { DEBUG(1, ("Authenticatoin for [%s] -> [%s]\\[%s] in our domain failed - we can't find our domain!\n", - state->request.data.auth.user, name_domain, name_user)); + state->request.data.auth_crap.user, name_domain, name_user)); result = NT_STATUS_NO_SUCH_USER; goto done; } @@ -434,6 +532,13 @@ enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state) netsamlogon_cache_store( cli->mem_ctx, &info3 ); wcache_invalidate_samlogon(find_domain_from_name(name_domain), &info3); + if (!NT_STATUS_IS_OK(result = check_info3_in_group(mem_ctx, &info3, state->request.data.auth_crap.required_membership_sid))) { + DEBUG(3, ("User %s is not in the required group (%s), so plaintext authentication is rejected\n", + state->request.data.auth_crap.user, + state->request.data.auth_crap.required_membership_sid)); + goto done; + } + if (state->request.flags & WBFLAG_PAM_INFO3_NDR) { result = append_info3_as_ndr(mem_ctx, state, &info3); } else if (state->request.flags & WBFLAG_PAM_UNIX_NAME) { @@ -477,6 +582,10 @@ done: if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) { result = NT_STATUS_NO_LOGON_SERVERS; } + + if (state->request.flags & WBFLAG_PAM_NT_STATUS_SQUASH) { + result = nt_status_squash(result); + } state->response.data.auth.nt_status = NT_STATUS_V(result); push_utf8_fstring(state->response.data.auth.nt_status_string, nt_errstr(result)); diff --git a/source3/utils/ntlm_auth.c b/source3/utils/ntlm_auth.c index c06290c24e..c926d07698 100644 --- a/source3/utils/ntlm_auth.c +++ b/source3/utils/ntlm_auth.c @@ -3,8 +3,8 @@ Winbind status program. - Copyright (C) Tim Potter 2000-2002 - Copyright (C) Andrew Bartlett 2003 + Copyright (C) Tim Potter 2000-2003 + Copyright (C) Andrew Bartlett 2003-2004 Copyright (C) Francesco Chemolli 2000 This program is free software; you can redistribute it and/or modify @@ -91,6 +91,8 @@ static DATA_BLOB opt_nt_response; static int request_lm_key; static int request_nt_key; +static const char *require_membership_of; +static const char *require_membership_sid; static char winbind_separator(void) { @@ -138,7 +140,7 @@ static const char *get_winbind_domain(void) if (winbindd_request(WINBINDD_DOMAIN_NAME, NULL, &response) != NSS_STATUS_SUCCESS) { DEBUG(0, ("could not obtain winbind domain name!\n")); - return NULL; + exit(1); } fstrcpy(winbind_domain, response.data.domain_name); @@ -173,14 +175,79 @@ static const char *get_winbind_netbios_name(void) } +/* Copy of parse_domain_user from winbindd_util.c. Parse a string of the + form DOMAIN/user into a domain and a user */ + +static BOOL parse_ntlm_auth_domain_user(const char *domuser, fstring domain, + fstring user) +{ + + char *p = strchr(domuser,winbind_separator()); + + if (!p) { + return False; + } + + fstrcpy(user, p+1); + fstrcpy(domain, domuser); + domain[PTR_DIFF(p, domuser)] = 0; + strupper_m(domain); + + return True; +} + +static BOOL get_require_membership_sid(void) { + struct winbindd_request request; + struct winbindd_response response; + + if (!require_membership_of) { + return True; + } + + if (require_membership_sid) { + return True; + } + + /* Otherwise, ask winbindd for the name->sid request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + if (!parse_ntlm_auth_domain_user(require_membership_of, + request.data.name.dom_name, + request.data.name.name)) { + DEBUG(0, ("Could not parse %s into seperate domain/name parts!\n", + require_membership_of)); + return False; + } + + if (winbindd_request(WINBINDD_LOOKUPNAME, &request, &response) != + NSS_STATUS_SUCCESS) { + DEBUG(0, ("Winbindd lookupname failed to resolve %s into a SID!\n", + require_membership_of)); + return False; + } + + require_membership_sid = strdup(response.data.sid.sid); + + if (require_membership_sid) + return True; + + return False; +} /* Authenticate a user with a plaintext password */ -static BOOL check_plaintext_auth(const char *user, const char *pass, BOOL stdout_diagnostics) +static BOOL check_plaintext_auth(const char *user, const char *pass, + BOOL stdout_diagnostics) { struct winbindd_request request; struct winbindd_response response; NSS_STATUS result; + if (!get_require_membership_sid()) { + return False; + } + /* Send off request */ ZERO_STRUCT(request); @@ -188,6 +255,8 @@ static BOOL check_plaintext_auth(const char *user, const char *pass, BOOL stdout fstrcpy(request.data.auth.user, user); fstrcpy(request.data.auth.pass, pass); + if (require_membership_sid) + fstrcpy(request.data.auth.required_membership_sid, require_membership_sid); result = winbindd_request(WINBINDD_PAM_AUTH, &request, &response); @@ -237,11 +306,18 @@ static NTSTATUS contact_winbind_auth_crap(const char *username, static uint8 zeros[16]; + if (!get_require_membership_sid()) { + return NT_STATUS_INVALID_PARAMETER; + } + ZERO_STRUCT(request); ZERO_STRUCT(response); request.flags = flags; + if (require_membership_sid) + fstrcpy(request.data.auth_crap.required_membership_sid, require_membership_sid); + if (push_utf8_fstring(request.data.auth_crap.user, username) == -1) { *error_string = smb_xstrdup( "unable to create utf8 string for username"); @@ -264,12 +340,16 @@ static NTSTATUS contact_winbind_auth_crap(const char *username, memcpy(request.data.auth_crap.chal, challenge->data, MIN(challenge->length, 8)); if (lm_response && lm_response->length) { - memcpy(request.data.auth_crap.lm_resp, lm_response->data, MIN(lm_response->length, sizeof(request.data.auth_crap.lm_resp))); + memcpy(request.data.auth_crap.lm_resp, + lm_response->data, + MIN(lm_response->length, sizeof(request.data.auth_crap.lm_resp))); request.data.auth_crap.lm_resp_len = lm_response->length; } if (nt_response && nt_response->length) { - memcpy(request.data.auth_crap.nt_resp, nt_response->data, MIN(nt_response->length, sizeof(request.data.auth_crap.nt_resp))); + memcpy(request.data.auth_crap.nt_resp, + nt_response->data, + MIN(nt_response->length, sizeof(request.data.auth_crap.nt_resp))); request.data.auth_crap.nt_resp_len = nt_response->length; } @@ -346,7 +426,9 @@ static NTSTATUS winbind_pw_check(struct ntlmssp_state *ntlmssp_state, DATA_BLOB } else { DEBUG(NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCESS_DENIED) ? 0 : 3, ("Login for user [%s]\\[%s]@[%s] failed due to [%s]\n", - ntlmssp_state->domain, ntlmssp_state->user, ntlmssp_state->workstation, error_string ? error_string : "unknown error (NULL)")); + ntlmssp_state->domain, ntlmssp_state->user, + ntlmssp_state->workstation, + error_string ? error_string : "unknown error (NULL)")); ntlmssp_state->auth_context = NULL; } return nt_status; @@ -382,7 +464,10 @@ static NTSTATUS local_pw_check(struct ntlmssp_state *ntlmssp_state, DATA_BLOB *u if (memcmp(nt_key, zeros, 16) != 0) { *user_session_key = data_blob(nt_key, 16); } - ntlmssp_state->auth_context = talloc_asprintf(ntlmssp_state->mem_ctx, "%s%c%s", ntlmssp_state->domain, *lp_winbind_separator(), ntlmssp_state->user); + ntlmssp_state->auth_context = talloc_asprintf(ntlmssp_state->mem_ctx, + "%s%c%s", ntlmssp_state->domain, + *lp_winbind_separator(), + ntlmssp_state->user); } else { DEBUG(3, ("Login for user [%s]\\[%s]@[%s] failed due to [%s]\n", ntlmssp_state->domain, ntlmssp_state->user, ntlmssp_state->workstation, @@ -1381,6 +1466,8 @@ static BOOL check_auth_crap(void) if (request_nt_key) flags |= WBFLAG_PAM_USER_SESSION_KEY; + flags |= WBFLAG_PAM_NT_STATUS_SQUASH; + nt_status = contact_winbind_auth_crap(opt_username, opt_domain, opt_workstation, &opt_challenge, @@ -1773,7 +1860,7 @@ static BOOL test_lmv2_ntlmv2_broken(enum ntlm_break break_which) if (break_which != NO_NT && break_which != BREAK_NT && memcmp(user_session_key.data, nt_key, sizeof(nt_key)) != 0) { - DEBUG(1, ("USER Session Key does not match expectations!\n")); + DEBUG(1, ("USER (NT) Session Key does not match expectations!\n")); DEBUG(1, ("nt_key:\n")); dump_data(1, (const char *)nt_key, 16); DEBUG(1, ("expected:\n")); @@ -2008,7 +2095,8 @@ enum { OPT_PASSWORD, OPT_LM_KEY, OPT_NT_KEY, - OPT_DIAGNOSTICS + OPT_DIAGNOSTICS, + OPT_REQUIRE_MEMBERSHIP }; int main(int argc, const char **argv) @@ -2020,12 +2108,6 @@ enum { static const char *hex_challenge; static const char *hex_lm_response; static const char *hex_nt_response; - char *challenge; - char *lm_response; - char *nt_response; - size_t challenge_len; - size_t lm_response_len; - size_t nt_response_len; poptContext pc; @@ -2050,6 +2132,7 @@ enum { { "request-lm-key", 0, POPT_ARG_NONE, &request_lm_key, OPT_LM_KEY, "Retreive LM session key"}, { "request-nt-key", 0, POPT_ARG_NONE, &request_nt_key, OPT_NT_KEY, "Retreive NT session key"}, { "diagnostics", 0, POPT_ARG_NONE, &diagnostics, OPT_DIAGNOSTICS, "Perform diagnostics on the authentictaion chain"}, + { "require-membership-of", 0, POPT_ARG_STRING, &require_membership_of, OPT_REQUIRE_MEMBERSHIP, "Require that a user be a member of this group (either name or SID) for authentication to succeed" }, POPT_COMMON_SAMBA POPT_TABLEEND }; @@ -2083,40 +2166,32 @@ enum { while((opt = poptGetNextOpt(pc)) != -1) { switch (opt) { case OPT_CHALLENGE: - challenge = smb_xmalloc((strlen(hex_challenge))/2+1); - if ((challenge_len = strhex_to_str(challenge, - strlen(hex_challenge), - hex_challenge)) != 8) { - x_fprintf(x_stderr, "hex decode of %s failed (only got %lu bytes)!\n", - hex_challenge, (unsigned long)challenge_len); + opt_challenge = strhex_to_data_blob(hex_challenge); + if (opt_challenge.length != 8) { + x_fprintf(x_stderr, "hex decode of %s failed!\n", hex_challenge); exit(1); } - opt_challenge = data_blob(challenge, challenge_len); - SAFE_FREE(challenge); break; case OPT_LM: - lm_response = smb_xmalloc((strlen(hex_lm_response))/2+1); - lm_response_len = strhex_to_str(lm_response, - strlen(hex_lm_response), - hex_lm_response); - if (lm_response_len != 24) { + opt_lm_response = strhex_to_data_blob(hex_lm_response); + if (opt_lm_response.length != 24) { x_fprintf(x_stderr, "hex decode of %s failed!\n", hex_lm_response); exit(1); } - opt_lm_response = data_blob(lm_response, lm_response_len); - SAFE_FREE(lm_response); break; + case OPT_NT: - nt_response = smb_xmalloc((strlen(hex_nt_response)+2)/2+1); - nt_response_len = strhex_to_str(nt_response, - strlen(hex_nt_response), - hex_nt_response); - if (nt_response_len < 24) { + opt_nt_response = strhex_to_data_blob(hex_nt_response); + if (opt_nt_response.length < 24) { x_fprintf(x_stderr, "hex decode of %s failed!\n", hex_nt_response); exit(1); } - opt_nt_response = data_blob(nt_response, nt_response_len); - SAFE_FREE(nt_response); + break; + + case OPT_REQUIRE_MEMBERSHIP: + if (StrnCaseCmp("S-", require_membership_of, 2) == 0) { + require_membership_sid = require_membership_of; + } break; } } -- cgit