mirror of
https://github.com/samba-team/samba.git
synced 2025-01-29 21:47:30 +03:00
f804b590f9
to handle domain trusts. Jeremy and I talked about this and it's going in as working code. It keeps winbind clean and solves the trust problem with minimal changes. To summarize, there are 2 basic cases where the deadlock would occur. (1) lookuping up secondary groups for a user, and (2) get[gr|pw]nam() calls that fall through the NSS layer because they don't exist anywhere. o To handle case #1, we bypass winbindd in sys_getgrouplist() unless the username includes the 'winbind separator'. o Case #2 is handled by adding checks in winbindd to return failure if we are a DC and the domain matches our own. This code has been tested using basic share connections, domain logons, and with pam_winbind (both with and without 'winbind use default domain'). The 'trustdomain' auth module should work as well if an admin wants to manually create UNIX users for acounts in the trusted domains. Other misc fixes: * we need to fix check_ntlm_password() to be able to determine if an auth module is authoritative over a user (NT_STATUS_WRONG_PASSWORD, etc...). I worked around my specific situation, but this needs to be fixed. the winbindd auth module was causing delays. * fix named server mutex deadlock between trust domain auth module and winbindd looking up a uid * make sure SAM_ACCOUNT gets stored in the server_info struct for the _net_sam_logon() reply. Configuration details: The recommended method for supporting trusts is to use winbind. The gets us around some of the server mutex issues as well. * set 'files winbind' for passwd: and group: in /etc/nsswitch.conf * create domain trusts like normal * join winbind on the pdc to the Samba domain using 'net rpc join' * add normal parameters to smb.conf for winbind * set 'auth method = guest sam winbind' * start smbd, nmbd, & winbindd Problems that remain: * join a Windows 2k/XP box to a Samba domain. * create a 2-way trust between the Samba domain and an NT domain * logon to the windows client as a user from theh trusted domain * try to browse server in the trusted domain (or other workstations). an NT client seems to work ok, but 2k and XP either prompt for passwords or fail with errors. apparanently this never got tested since no one has ever been able to logon as a trusted user to a Samba domain from a Windows client.
-
440 lines
14 KiB
C
440 lines
14 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
Winbind daemon - pam auth funcions
|
|
|
|
Copyright (C) Andrew Tridgell 2000
|
|
Copyright (C) Tim Potter 2001
|
|
Copyright (C) Andrew Bartlett 2001-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 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 "winbindd.h"
|
|
#undef DBGC_CLASS
|
|
#define DBGC_CLASS DBGC_WINBIND
|
|
|
|
|
|
static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx,
|
|
struct winbindd_cli_state *state,
|
|
NET_USER_INFO_3 *info3)
|
|
{
|
|
prs_struct ps;
|
|
uint32 size;
|
|
if (!prs_init(&ps, 256 /* Random, non-zero number */, mem_ctx, MARSHALL)) {
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
if (!net_io_user_info3("", info3, &ps, 1, 3)) {
|
|
prs_mem_free(&ps);
|
|
return NT_STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
size = prs_data_size(&ps);
|
|
state->response.extra_data = malloc(size);
|
|
if (!state->response.extra_data) {
|
|
prs_mem_free(&ps);
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
prs_copy_all_data_out(state->response.extra_data, &ps);
|
|
state->response.length += size;
|
|
prs_mem_free(&ps);
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
/* Return a password structure from a username. */
|
|
|
|
enum winbindd_result winbindd_pam_auth(struct winbindd_cli_state *state)
|
|
{
|
|
NTSTATUS result;
|
|
fstring name_domain, name_user;
|
|
unsigned char trust_passwd[16];
|
|
time_t last_change_time;
|
|
uint32 sec_channel_type;
|
|
NET_USER_INFO_3 info3;
|
|
struct cli_state *cli = NULL;
|
|
uchar chal[8];
|
|
TALLOC_CTX *mem_ctx = NULL;
|
|
DATA_BLOB lm_resp;
|
|
DATA_BLOB nt_resp;
|
|
DOM_CRED ret_creds;
|
|
int attempts = 0;
|
|
|
|
/* Ensure null termination */
|
|
state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0';
|
|
|
|
/* Ensure null termination */
|
|
state->request.data.auth.pass[sizeof(state->request.data.auth.pass)-1]='\0';
|
|
|
|
DEBUG(3, ("[%5d]: pam auth %s\n", state->pid,
|
|
state->request.data.auth.user));
|
|
|
|
if (!(mem_ctx = talloc_init("winbind pam auth for %s", state->request.data.auth.user))) {
|
|
DEBUG(0, ("winbindd_pam_auth: could not talloc_init()!\n"));
|
|
result = NT_STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
|
|
/* Parse domain and username */
|
|
|
|
if (!parse_domain_user(state->request.data.auth.user, name_domain,
|
|
name_user)) {
|
|
DEBUG(5,("no domain separator (%s) in username (%s) - failing auth\n", lp_winbind_separator(), state->request.data.auth.user));
|
|
result = NT_STATUS_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
{
|
|
unsigned char local_lm_response[24];
|
|
unsigned char local_nt_response[24];
|
|
|
|
generate_random_buffer(chal, 8, False);
|
|
SMBencrypt(state->request.data.auth.pass, chal, local_lm_response);
|
|
|
|
SMBNTencrypt(state->request.data.auth.pass, chal, local_nt_response);
|
|
|
|
lm_resp = data_blob_talloc(mem_ctx, local_lm_response, sizeof(local_lm_response));
|
|
nt_resp = data_blob_talloc(mem_ctx, local_nt_response, sizeof(local_nt_response));
|
|
}
|
|
|
|
/*
|
|
* Get the machine account password for our primary domain
|
|
*/
|
|
|
|
if (!secrets_fetch_trust_account_password(
|
|
lp_workgroup(), trust_passwd, &last_change_time,
|
|
&sec_channel_type)) {
|
|
DEBUG(0, ("winbindd_pam_auth: could not fetch trust account "
|
|
"password for domain %s\n", lp_workgroup()));
|
|
result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
|
goto done;
|
|
}
|
|
|
|
do {
|
|
ZERO_STRUCT(info3);
|
|
ZERO_STRUCT(ret_creds);
|
|
|
|
/* Don't shut this down - it belongs to the connection cache code */
|
|
result = cm_get_netlogon_cli(lp_workgroup(), trust_passwd,
|
|
sec_channel_type, False, &cli);
|
|
|
|
if (!NT_STATUS_IS_OK(result)) {
|
|
DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
|
|
goto done;
|
|
}
|
|
|
|
result = cli_netlogon_sam_network_logon(cli, mem_ctx,
|
|
&ret_creds,
|
|
name_user, name_domain,
|
|
global_myname(), chal,
|
|
lm_resp, nt_resp,
|
|
&info3);
|
|
attempts += 1;
|
|
|
|
/* We have to try a second time as cm_get_netlogon_cli
|
|
might not yet have noticed that the DC has killed
|
|
our connection. */
|
|
|
|
} while ( (attempts < 2) && (cli->fd == -1) );
|
|
|
|
|
|
clnt_deal_with_creds(cli->sess_key, &(cli->clnt_cred), &ret_creds);
|
|
|
|
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;
|
|
}
|
|
|
|
state->response.data.auth.nt_status = NT_STATUS_V(result);
|
|
fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
|
|
fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
|
|
state->response.data.auth.pam_error = nt_status_to_pam(result);
|
|
|
|
DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2, ("Plain-text authentication for user %s returned %s (PAM: %d)\n",
|
|
state->request.data.auth.user,
|
|
state->response.data.auth.nt_status_string,
|
|
state->response.data.auth.pam_error));
|
|
|
|
if (mem_ctx)
|
|
talloc_destroy(mem_ctx);
|
|
|
|
return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
|
|
}
|
|
|
|
/* Challenge Response Authentication Protocol */
|
|
|
|
enum winbindd_result winbindd_pam_auth_crap(struct winbindd_cli_state *state)
|
|
{
|
|
NTSTATUS result;
|
|
unsigned char trust_passwd[16];
|
|
time_t last_change_time;
|
|
uint32 sec_channel_type;
|
|
NET_USER_INFO_3 info3;
|
|
struct cli_state *cli = NULL;
|
|
TALLOC_CTX *mem_ctx = NULL;
|
|
char *user = NULL;
|
|
const char *domain = NULL;
|
|
const char *contact_domain;
|
|
const char *workstation;
|
|
DOM_CRED ret_creds;
|
|
int attempts = 0;
|
|
|
|
DATA_BLOB lm_resp, nt_resp;
|
|
|
|
if (!state->privileged) {
|
|
DEBUG(2, ("winbindd_pam_auth_crap: non-privileged access denied!\n"));
|
|
/* send a better message than ACCESS_DENIED */
|
|
push_utf8_fstring(state->response.data.auth.error_string, "winbind client not authorized to use winbindd_pam_auth_crap");
|
|
result = NT_STATUS_ACCESS_DENIED;
|
|
goto done;
|
|
}
|
|
|
|
/* Ensure null termination */
|
|
state->request.data.auth_crap.user[sizeof(state->request.data.auth_crap.user)-1]='\0';
|
|
|
|
/* Ensure null termination */
|
|
state->request.data.auth_crap.domain[sizeof(state->request.data.auth_crap.domain)-1]='\0';
|
|
|
|
if (!(mem_ctx = talloc_init("winbind pam auth crap for (utf8) %s", state->request.data.auth_crap.user))) {
|
|
DEBUG(0, ("winbindd_pam_auth_crap: could not talloc_init()!\n"));
|
|
result = NT_STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
|
|
if (pull_utf8_talloc(mem_ctx, &user, state->request.data.auth_crap.user) == (size_t)-1) {
|
|
DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
|
|
}
|
|
|
|
if (*state->request.data.auth_crap.domain) {
|
|
char *dom = NULL;
|
|
if (pull_utf8_talloc(mem_ctx, &dom, state->request.data.auth_crap.domain) == (size_t)-1) {
|
|
DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
|
|
}
|
|
domain = dom;
|
|
} else if (lp_winbind_use_default_domain()) {
|
|
domain = lp_workgroup();
|
|
} else {
|
|
DEBUG(5,("no domain specified with username (%s) - failing auth\n",
|
|
user));
|
|
result = NT_STATUS_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
DEBUG(3, ("[%5d]: pam auth crap domain: %s user: %s\n", state->pid,
|
|
domain, user));
|
|
|
|
/* check our role as a domain member first */
|
|
if ( lp_server_role() == ROLE_DOMAIN_MEMBER ) {
|
|
if ( !lp_allow_trusted_domains() && !strequal(domain, lp_workgroup()) ) {
|
|
DEBUG(5,("winbindd_pam_auth_crap: failing autghentication becuase of disallowed trust domains\n"));
|
|
result = NT_STATUS_LOGON_FAILURE;
|
|
goto done;
|
|
}
|
|
|
|
contact_domain = domain;
|
|
|
|
/*
|
|
* Get the machine account password for the domain to contact.
|
|
* This is either our own domain for a workstation, or possibly
|
|
* any domain for a PDC with trusted domains.
|
|
*/
|
|
|
|
if (!secrets_fetch_trust_account_password (contact_domain,
|
|
trust_passwd,
|
|
&last_change_time,
|
|
&sec_channel_type)) {
|
|
DEBUG(0, ("winbindd_pam_auth_crap: could not fetch trust account "
|
|
"password for domain %s\n", contact_domain));
|
|
result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
|
goto done;
|
|
}
|
|
}
|
|
else if ( lp_allow_trusted_domains() ) {
|
|
/* if we are not a domain member, then we must be a DC. Must never
|
|
see a logon for our domain */
|
|
DOM_SID sid;
|
|
char *pwd;
|
|
contact_domain = domain;
|
|
|
|
if (!secrets_fetch_trusted_domain_password (contact_domain,
|
|
&pwd, &sid,
|
|
&last_change_time)) {
|
|
DEBUG(0, ("winbindd_pam_auth_crap: could not fetch trust account "
|
|
"password for domain %s\n", contact_domain));
|
|
result = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
|
|
goto done;
|
|
}
|
|
sec_channel_type = SEC_CHAN_DOMAIN;
|
|
E_md4hash(pwd, trust_passwd);
|
|
SAFE_FREE(pwd);
|
|
|
|
}
|
|
|
|
if (*state->request.data.auth_crap.workstation) {
|
|
char *wrk = NULL;
|
|
if (pull_utf8_talloc(mem_ctx, &wrk, state->request.data.auth_crap.workstation) == (size_t)-1) {
|
|
DEBUG(0, ("winbindd_pam_auth_crap: pull_utf8_talloc failed!\n"));
|
|
}
|
|
workstation = wrk;
|
|
} else {
|
|
workstation = global_myname();
|
|
}
|
|
|
|
if (state->request.data.auth_crap.lm_resp_len > sizeof(state->request.data.auth_crap.lm_resp)
|
|
|| state->request.data.auth_crap.nt_resp_len > sizeof(state->request.data.auth_crap.nt_resp)) {
|
|
DEBUG(0, ("winbindd_pam_auth_crap: invalid password length %u/%u\n",
|
|
state->request.data.auth_crap.lm_resp_len,
|
|
state->request.data.auth_crap.nt_resp_len));
|
|
result = NT_STATUS_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
lm_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.lm_resp, state->request.data.auth_crap.lm_resp_len);
|
|
nt_resp = data_blob_talloc(mem_ctx, state->request.data.auth_crap.nt_resp, state->request.data.auth_crap.nt_resp_len);
|
|
|
|
do {
|
|
ZERO_STRUCT(info3);
|
|
ZERO_STRUCT(ret_creds);
|
|
|
|
/* Don't shut this down - it belongs to the connection cache code */
|
|
result = cm_get_netlogon_cli(contact_domain, trust_passwd,
|
|
sec_channel_type, False, &cli);
|
|
|
|
if (!NT_STATUS_IS_OK(result)) {
|
|
DEBUG(3, ("could not open handle to NETLOGON pipe (error: %s)\n",
|
|
nt_errstr(result)));
|
|
goto done;
|
|
}
|
|
|
|
result = cli_netlogon_sam_network_logon(cli, mem_ctx,
|
|
&ret_creds,
|
|
user, domain,
|
|
workstation,
|
|
state->request.data.auth_crap.chal,
|
|
lm_resp, nt_resp,
|
|
&info3);
|
|
|
|
attempts += 1;
|
|
|
|
/* We have to try a second time as cm_get_netlogon_cli
|
|
might not yet have noticed that the DC has killed
|
|
our connection. */
|
|
|
|
} while ( (attempts < 2) && (cli->fd == -1) );
|
|
|
|
clnt_deal_with_creds(cli->sess_key, &(cli->clnt_cred), &ret_creds);
|
|
|
|
if (NT_STATUS_IS_OK(result)) {
|
|
netsamlogon_cache_store( cli->mem_ctx, &info3 );
|
|
wcache_invalidate_samlogon(find_domain_from_name(domain), &info3);
|
|
|
|
if (state->request.data.auth_crap.flags & WINBIND_PAM_INFO3_NDR) {
|
|
result = append_info3_as_ndr(mem_ctx, state, &info3);
|
|
}
|
|
|
|
if (state->request.data.auth_crap.flags & WINBIND_PAM_NTKEY) {
|
|
memcpy(state->response.data.auth.nt_session_key, info3.user_sess_key, sizeof(state->response.data.auth.nt_session_key) /* 16 */);
|
|
}
|
|
if (state->request.data.auth_crap.flags & WINBIND_PAM_LMKEY) {
|
|
memcpy(state->response.data.auth.first_8_lm_hash, info3.padding, sizeof(state->response.data.auth.first_8_lm_hash) /* 8 */);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
state->response.data.auth.nt_status = NT_STATUS_V(result);
|
|
push_utf8_fstring(state->response.data.auth.nt_status_string, nt_errstr(result));
|
|
if (!*state->response.data.auth.error_string)
|
|
push_utf8_fstring(state->response.data.auth.error_string, get_friendly_nt_error_msg(result));
|
|
state->response.data.auth.pam_error = nt_status_to_pam(result);
|
|
|
|
DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2,
|
|
("NTLM CRAP authentication for user [%s]\\[%s] returned %s (PAM: %d)\n",
|
|
domain,
|
|
user,
|
|
state->response.data.auth.nt_status_string,
|
|
state->response.data.auth.pam_error));
|
|
|
|
if (mem_ctx)
|
|
talloc_destroy(mem_ctx);
|
|
|
|
return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
|
|
}
|
|
|
|
/* Change a user password */
|
|
|
|
enum winbindd_result winbindd_pam_chauthtok(struct winbindd_cli_state *state)
|
|
{
|
|
NTSTATUS result;
|
|
char *oldpass, *newpass;
|
|
fstring domain, user;
|
|
CLI_POLICY_HND *hnd;
|
|
|
|
DEBUG(3, ("[%5d]: pam chauthtok %s\n", state->pid,
|
|
state->request.data.chauthtok.user));
|
|
|
|
/* Setup crap */
|
|
|
|
if (state == NULL)
|
|
return WINBINDD_ERROR;
|
|
|
|
if (!parse_domain_user(state->request.data.chauthtok.user, domain,
|
|
user)) {
|
|
result = NT_STATUS_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
/* Change password */
|
|
|
|
oldpass = state->request.data.chauthtok.oldpass;
|
|
newpass = state->request.data.chauthtok.newpass;
|
|
|
|
/* Get sam handle */
|
|
|
|
if ( NT_STATUS_IS_ERR(result = cm_get_sam_handle(domain, &hnd)) ) {
|
|
DEBUG(1, ("could not get SAM handle on DC for %s\n", domain));
|
|
goto done;
|
|
}
|
|
|
|
if (!cli_oem_change_password(hnd->cli, user, newpass, oldpass)) {
|
|
DEBUG(1, ("password change failed for user %s/%s\n", domain,
|
|
user));
|
|
result = NT_STATUS_WRONG_PASSWORD;
|
|
} else {
|
|
result = NT_STATUS_OK;
|
|
}
|
|
|
|
done:
|
|
state->response.data.auth.nt_status = NT_STATUS_V(result);
|
|
fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result));
|
|
fstrcpy(state->response.data.auth.error_string, nt_errstr(result));
|
|
state->response.data.auth.pam_error = nt_status_to_pam(result);
|
|
|
|
DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2,
|
|
("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n",
|
|
domain,
|
|
user,
|
|
state->response.data.auth.nt_status_string,
|
|
state->response.data.auth.pam_error));
|
|
|
|
return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR;
|
|
}
|