From 0dc8f720e195c29d4d0422f9ae49df44543a1b6c Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 13 Jul 2006 09:29:25 +0000 Subject: r17005: Add a new helper mode to ntlm_auth: ntlm-change-password-1 This mode proxies pre-calculated blobs from a remote (probably VPN) client into the domain. This allows clients to change their password over a PPTP connection (where they would not be able to connect to SAMR directly). The precalculated blobs do not reveal the plaintext password. Original patch by Alexey Kobozev (This used to be commit 967292b7136c5100c0b9a2783c34b1948b16dad4) --- source3/nsswitch/winbindd.c | 1 + source3/nsswitch/winbindd_dual.c | 1 + source3/nsswitch/winbindd_nss.h | 13 ++ source3/nsswitch/winbindd_pam.c | 148 ++++++++++++++++++++ source3/rpc_client/cli_samr.c | 44 ++++++ source3/utils/ntlm_auth.c | 296 ++++++++++++++++++++++++++++++++++++++- 6 files changed, 502 insertions(+), 1 deletion(-) diff --git a/source3/nsswitch/winbindd.c b/source3/nsswitch/winbindd.c index e5582915b2..a4cd724e00 100644 --- a/source3/nsswitch/winbindd.c +++ b/source3/nsswitch/winbindd.c @@ -214,6 +214,7 @@ static struct winbindd_dispatch_table { { WINBINDD_PAM_AUTH_CRAP, winbindd_pam_auth_crap, "AUTH_CRAP" }, { WINBINDD_PAM_CHAUTHTOK, winbindd_pam_chauthtok, "CHAUTHTOK" }, { WINBINDD_PAM_LOGOFF, winbindd_pam_logoff, "PAM_LOGOFF" }, + { WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP, winbindd_pam_chng_pswd_auth_crap, "CHNG_PSWD_AUTH_CRAP" }, /* Enumeration functions */ diff --git a/source3/nsswitch/winbindd_dual.c b/source3/nsswitch/winbindd_dual.c index 5908c78d9a..b4d4a794ae 100644 --- a/source3/nsswitch/winbindd_dual.c +++ b/source3/nsswitch/winbindd_dual.c @@ -353,6 +353,7 @@ static struct winbindd_child_dispatch_table child_dispatch_table[] = { { WINBINDD_PAM_AUTH, winbindd_dual_pam_auth, "PAM_AUTH" }, { WINBINDD_PAM_AUTH_CRAP, winbindd_dual_pam_auth_crap, "AUTH_CRAP" }, { WINBINDD_PAM_LOGOFF, winbindd_dual_pam_logoff, "PAM_LOGOFF" }, + { WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP,winbindd_dual_pam_chng_pswd_auth_crap,"CHNG_PSWD_AUTH_CRAP" }, { WINBINDD_CHECK_MACHACC, winbindd_dual_check_machine_acct, "CHECK_MACHACC" }, { WINBINDD_DUAL_SID2UID, winbindd_dual_sid2uid, "DUAL_SID2UID" }, { WINBINDD_DUAL_SID2GID, winbindd_dual_sid2gid, "DUAL_SID2GID" }, diff --git a/source3/nsswitch/winbindd_nss.h b/source3/nsswitch/winbindd_nss.h index 6167a10c46..be0bb11948 100644 --- a/source3/nsswitch/winbindd_nss.h +++ b/source3/nsswitch/winbindd_nss.h @@ -65,6 +65,7 @@ enum winbindd_cmd { WINBINDD_PAM_AUTH_CRAP, WINBINDD_PAM_CHAUTHTOK, WINBINDD_PAM_LOGOFF, + WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP, /* List various things */ @@ -225,6 +226,18 @@ struct winbindd_request { fstring oldpass; fstring newpass; } chauthtok; /* pam_winbind passwd module */ + struct { + fstring user; + fstring domain; + unsigned char new_nt_pswd[516]; + uint16 new_nt_pswd_len; + unsigned char old_nt_hash_enc[16]; + uint16 old_nt_hash_enc_len; + unsigned char new_lm_pswd[516]; + uint16 new_lm_pswd_len; + unsigned char old_lm_hash_enc[16]; + uint16 old_lm_hash_enc_len; + } chng_pswd_auth_crap;/* pam_winbind passwd module */ struct { fstring user; fstring krb5ccname; diff --git a/source3/nsswitch/winbindd_pam.c b/source3/nsswitch/winbindd_pam.c index 34d23ebf8f..6af8e31bc0 100644 --- a/source3/nsswitch/winbindd_pam.c +++ b/source3/nsswitch/winbindd_pam.c @@ -1901,3 +1901,151 @@ process_result: return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR; } +/* Change user password with auth crap*/ + +void winbindd_pam_chng_pswd_auth_crap(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain = NULL; + const char *domain_name = NULL; + + /* Ensure null termination */ + state->request.data.chng_pswd_auth_crap.user[ + sizeof(state->request.data.chng_pswd_auth_crap.user)-1]=0; + state->request.data.chng_pswd_auth_crap.domain[ + sizeof(state->request.data.chng_pswd_auth_crap.domain)-1]=0; + + DEBUG(3, ("[%5lu]: pam change pswd auth crap domain: %s user: %s\n", + (unsigned long)state->pid, + state->request.data.chng_pswd_auth_crap.domain, + state->request.data.chng_pswd_auth_crap.user)); + + if (*state->request.data.chng_pswd_auth_crap.domain != '\0') { + domain_name = state->request.data.chng_pswd_auth_crap.domain; + } else if (lp_winbind_use_default_domain()) { + domain_name = lp_workgroup(); + } + + if (domain_name != NULL) + domain = find_domain_from_name(domain_name); + + if (domain != NULL) { + DEBUG(7, ("[%5lu]: pam auth crap changing pswd in domain: " + "%s\n", (unsigned long)state->pid,domain->name)); + sendto_domain(state, domain); + return; + } + + set_auth_errors(&state->response, NT_STATUS_NO_SUCH_USER); + DEBUG(5, ("CRAP change password for %s\\%s returned %s (PAM: %d)\n", + state->request.data.chng_pswd_auth_crap.domain, + state->request.data.chng_pswd_auth_crap.user, + state->response.data.auth.nt_status_string, + state->response.data.auth.pam_error)); + request_error(state); + return; +} + +enum winbindd_result winbindd_dual_pam_chng_pswd_auth_crap(struct winbindd_domain *domainSt, struct winbindd_cli_state *state) +{ + NTSTATUS result; + DATA_BLOB new_nt_password; + DATA_BLOB old_nt_hash_enc; + DATA_BLOB new_lm_password; + DATA_BLOB old_lm_hash_enc; + fstring domain,user; + POLICY_HND dom_pol; + struct winbindd_domain *contact_domain = domainSt; + struct rpc_pipe_client *cli; + + /* Ensure null termination */ + state->request.data.chng_pswd_auth_crap.user[ + sizeof(state->request.data.chng_pswd_auth_crap.user)-1]=0; + state->request.data.chng_pswd_auth_crap.domain[ + sizeof(state->request.data.chng_pswd_auth_crap.domain)-1]=0; + *domain = 0; + *user = 0; + + DEBUG(3, ("[%5lu]: pam change pswd auth crap domain: %s user: %s\n", + (unsigned long)state->pid, + state->request.data.chng_pswd_auth_crap.domain, + state->request.data.chng_pswd_auth_crap.user)); + + if (*state->request.data.chng_pswd_auth_crap.domain) { + fstrcpy(domain,state->request.data.chng_pswd_auth_crap.domain); + } else { + parse_domain_user(state->request.data.chng_pswd_auth_crap.user, + domain, user); + + if(!*domain) { + DEBUG(3,("no domain specified with username (%s) - " + "failing auth\n", + state->request.data.chng_pswd_auth_crap.user)); + result = NT_STATUS_NO_SUCH_USER; + goto done; + } + } + + if (!*domain && lp_winbind_use_default_domain()) { + fstrcpy(domain,(char *)lp_workgroup()); + } + + if(!*user) { + fstrcpy(user, state->request.data.chng_pswd_auth_crap.user); + } + + DEBUG(3, ("[%5lu]: pam auth crap domain: %s user: %s\n", + (unsigned long)state->pid, domain, user)); + + /* Change password */ + new_nt_password = data_blob_talloc( + state->mem_ctx, + state->request.data.chng_pswd_auth_crap.new_nt_pswd, + state->request.data.chng_pswd_auth_crap.new_nt_pswd_len); + + old_nt_hash_enc = data_blob_talloc( + state->mem_ctx, + state->request.data.chng_pswd_auth_crap.old_nt_hash_enc, + state->request.data.chng_pswd_auth_crap.old_nt_hash_enc_len); + + if(state->request.data.chng_pswd_auth_crap.new_lm_pswd_len > 0) { + new_lm_password = data_blob_talloc( + state->mem_ctx, + state->request.data.chng_pswd_auth_crap.new_lm_pswd, + state->request.data.chng_pswd_auth_crap.new_lm_pswd_len); + + old_lm_hash_enc = data_blob_talloc( + state->mem_ctx, + state->request.data.chng_pswd_auth_crap.old_lm_hash_enc, + state->request.data.chng_pswd_auth_crap.old_lm_hash_enc_len); + } else { + new_lm_password.length = 0; + old_lm_hash_enc.length = 0; + } + + /* Get sam handle */ + + result = cm_connect_sam(contact_domain, state->mem_ctx, &cli, &dom_pol); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1, ("could not get SAM handle on DC for %s\n", domain)); + goto done; + } + + result = rpccli_samr_chng_pswd_auth_crap( + cli, state->mem_ctx, user, new_nt_password, old_nt_hash_enc, + new_lm_password, old_lm_hash_enc); + + 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, + 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, + ("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; +} diff --git a/source3/rpc_client/cli_samr.c b/source3/rpc_client/cli_samr.c index ea8db63642..4c6a868e7f 100644 --- a/source3/rpc_client/cli_samr.c +++ b/source3/rpc_client/cli_samr.c @@ -1288,6 +1288,50 @@ NTSTATUS rpccli_samr_chgpasswd_user(struct rpc_pipe_client *cli, return result; } +/* User change passwd with auth crap */ + +NTSTATUS rpccli_samr_chng_pswd_auth_crap(struct rpc_pipe_client *cli, + TALLOC_CTX *mem_ctx, + const char *username, + DATA_BLOB new_nt_password, + DATA_BLOB old_nt_hash_enc, + DATA_BLOB new_lm_password, + DATA_BLOB old_lm_hash_enc) +{ + prs_struct qbuf, rbuf; + SAMR_Q_CHGPASSWD_USER q; + SAMR_R_CHGPASSWD_USER r; + char *srv_name_slash; + + if (!(srv_name_slash = talloc_asprintf(mem_ctx, "\\\\%s", + cli->cli->desthost))) { + return NT_STATUS_NO_MEMORY; + } + + DEBUG(5,("rpccli_samr_chng_pswd_auth_crap on server: %s\n", + srv_name_slash)); + + ZERO_STRUCT(q); + ZERO_STRUCT(r); + + /* Marshall data and send request */ + + init_samr_q_chgpasswd_user(&q, srv_name_slash, username, + new_nt_password.data, + old_nt_hash_enc.data, + new_lm_password.data, + old_lm_hash_enc.data); + + CLI_DO_RPC(cli, mem_ctx, PI_SAMR, SAMR_CHGPASSWD_USER, + q, r, + qbuf, rbuf, + samr_io_q_chgpasswd_user, + samr_io_r_chgpasswd_user, + NT_STATUS_UNSUCCESSFUL); + + return r.status; +} + /* change password 3 */ NTSTATUS rpccli_samr_chgpasswd3(struct rpc_pipe_client *cli, diff --git a/source3/utils/ntlm_auth.c b/source3/utils/ntlm_auth.c index fc9e3e9546..9e178ec945 100644 --- a/source3/utils/ntlm_auth.c +++ b/source3/utils/ntlm_auth.c @@ -38,6 +38,7 @@ enum stdio_helper_mode { GSS_SPNEGO, GSS_SPNEGO_CLIENT, NTLM_SERVER_1, + NTLM_CHANGE_PASSWORD_1, NUM_HELPER_MODES }; @@ -62,6 +63,8 @@ static void manage_gss_spnego_client_request (enum stdio_helper_mode stdio_helpe static void manage_ntlm_server_1_request (enum stdio_helper_mode stdio_helper_mode, char *buf, int length); +static void manage_ntlm_change_password_1_request(enum stdio_helper_mode helper_mode, char *buf, int length); + static const struct { enum stdio_helper_mode mode; const char *name; @@ -74,6 +77,7 @@ static const struct { { GSS_SPNEGO, "gss-spnego", manage_gss_spnego_request}, { GSS_SPNEGO_CLIENT, "gss-spnego-client", manage_gss_spnego_client_request}, { NTLM_SERVER_1, "ntlm-server-1", manage_ntlm_server_1_request}, + { NTLM_CHANGE_PASSWORD_1, "ntlm-change-password-1", manage_ntlm_change_password_1_request}, { NUM_HELPER_MODES, NULL, NULL} }; @@ -390,7 +394,87 @@ NTSTATUS contact_winbind_auth_crap(const char *username, free_response(&response); return nt_status; } - + +/* contact server to change user password using auth crap */ +static NTSTATUS contact_winbind_change_pswd_auth_crap(const char *username, + const char *domain, + const DATA_BLOB new_nt_pswd, + const DATA_BLOB old_nt_hash_enc, + const DATA_BLOB new_lm_pswd, + const DATA_BLOB old_lm_hash_enc, + char **error_string) +{ + NTSTATUS nt_status; + NSS_STATUS result; + struct winbindd_request request; + struct winbindd_response response; + + if (!get_require_membership_sid()) + { + if(error_string) + *error_string = smb_xstrdup("Can't get membership sid."); + return NT_STATUS_INVALID_PARAMETER; + } + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + if(username != NULL) + fstrcpy(request.data.chng_pswd_auth_crap.user, username); + if(domain != NULL) + fstrcpy(request.data.chng_pswd_auth_crap.domain,domain); + + if(new_nt_pswd.length) + { + memcpy(request.data.chng_pswd_auth_crap.new_nt_pswd, new_nt_pswd.data, sizeof(request.data.chng_pswd_auth_crap.new_nt_pswd)); + request.data.chng_pswd_auth_crap.new_nt_pswd_len = new_nt_pswd.length; + } + + if(old_nt_hash_enc.length) + { + memcpy(request.data.chng_pswd_auth_crap.old_nt_hash_enc, old_nt_hash_enc.data, sizeof(request.data.chng_pswd_auth_crap.old_nt_hash_enc)); + request.data.chng_pswd_auth_crap.old_nt_hash_enc_len = old_nt_hash_enc.length; + } + + if(new_lm_pswd.length) + { + memcpy(request.data.chng_pswd_auth_crap.new_lm_pswd, new_lm_pswd.data, sizeof(request.data.chng_pswd_auth_crap.new_lm_pswd)); + request.data.chng_pswd_auth_crap.new_lm_pswd_len = new_lm_pswd.length; + } + + if(old_lm_hash_enc.length) + { + memcpy(request.data.chng_pswd_auth_crap.old_lm_hash_enc, old_lm_hash_enc.data, sizeof(request.data.chng_pswd_auth_crap.old_lm_hash_enc)); + request.data.chng_pswd_auth_crap.old_lm_hash_enc_len = old_lm_hash_enc.length; + } + + result = winbindd_request_response(WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP, &request, &response); + + /* Display response */ + + if ((result != NSS_STATUS_SUCCESS) && (response.data.auth.nt_status == 0)) + { + nt_status = NT_STATUS_UNSUCCESSFUL; + if (error_string) + *error_string = smb_xstrdup("Reading winbind reply failed!"); + free_response(&response); + return nt_status; + } + + nt_status = (NT_STATUS(response.data.auth.nt_status)); + if (!NT_STATUS_IS_OK(nt_status)) + { + if (error_string) + *error_string = smb_xstrdup(response.data.auth.error_string); + free_response(&response); + return nt_status; + } + + free_response(&response); + + return nt_status; +} + static NTSTATUS winbind_pw_check(struct ntlmssp_state *ntlmssp_state, DATA_BLOB *user_session_key, DATA_BLOB *lm_session_key) { static const char zeros[16]; @@ -1580,6 +1664,216 @@ static void manage_ntlm_server_1_request(enum stdio_helper_mode stdio_helper_mod } } +static void manage_ntlm_change_password_1_request(enum stdio_helper_mode helper_mode, char *buf, int length) +{ + char *request, *parameter; + static DATA_BLOB new_nt_pswd; + static DATA_BLOB old_nt_hash_enc; + static DATA_BLOB new_lm_pswd; + static DATA_BLOB old_lm_hash_enc; + static char *full_username = NULL; + static char *username = NULL; + static char *domain = NULL; + static char *newpswd = NULL; + static char *oldpswd = NULL; + + if (strequal(buf, ".")) { + if(newpswd && oldpswd) { + uchar old_nt_hash[16]; + uchar old_lm_hash[16]; + uchar new_nt_hash[16]; + uchar new_lm_hash[16]; + + new_nt_pswd = data_blob(NULL, 516); + old_nt_hash_enc = data_blob(NULL, 16); + + /* Calculate the MD4 hash (NT compatible) of the + * password */ + E_md4hash(oldpswd, old_nt_hash); + E_md4hash(newpswd, new_nt_hash); + + /* E_deshash returns false for 'long' + passwords (> 14 DOS chars). + + Therefore, don't send a buffer + encrypted with the truncated hash + (it could allow an even easier + attack on the password) + + Likewise, obey the admin's restriction + */ + + if (lp_client_lanman_auth() && + E_deshash(newpswd, new_lm_hash) && + E_deshash(oldpswd, old_lm_hash)) { + new_lm_pswd = data_blob(NULL, 516); + old_lm_hash_enc = data_blob(NULL, 16); + encode_pw_buffer(new_lm_pswd.data, newpswd, + STR_UNICODE); + + SamOEMhash(new_lm_pswd.data, old_nt_hash, 516); + E_old_pw_hash(new_nt_hash, old_lm_hash, + old_lm_hash_enc.data); + } else { + new_lm_pswd.data = NULL; + new_lm_pswd.length = 0; + old_lm_hash_enc.data = NULL; + old_lm_hash_enc.length = 0; + } + + encode_pw_buffer(new_nt_pswd.data, newpswd, + STR_UNICODE); + + SamOEMhash(new_nt_pswd.data, old_nt_hash, 516); + E_old_pw_hash(new_nt_hash, old_nt_hash, + old_nt_hash_enc.data); + } + + if (!full_username && !username) { + x_fprintf(x_stdout, "Error: No username supplied!\n"); + } else if ((!new_nt_pswd.data || !old_nt_hash_enc.data) && + (!new_lm_pswd.data || old_lm_hash_enc.data) ) { + x_fprintf(x_stdout, "Error: No NT or LM password " + "blobs supplied!\n"); + } else { + char *error_string = NULL; + + if (full_username && !username) { + fstring fstr_user; + fstring fstr_domain; + + if (!parse_ntlm_auth_domain_user(full_username, + fstr_user, + fstr_domain)) { + /* username might be 'tainted', don't + * print into our new-line + * deleimianted stream */ + x_fprintf(x_stdout, "Error: Could not " + "parse into domain and " + "username\n"); + SAFE_FREE(username); + username = smb_xstrdup(full_username); + } else { + SAFE_FREE(username); + SAFE_FREE(domain); + username = smb_xstrdup(fstr_user); + domain = smb_xstrdup(fstr_domain); + } + + } + + if(!NT_STATUS_IS_OK(contact_winbind_change_pswd_auth_crap( + username, domain, + new_nt_pswd, + old_nt_hash_enc, + new_lm_pswd, + old_lm_hash_enc, + &error_string))) { + x_fprintf(x_stdout, "Password-Change: No\n"); + x_fprintf(x_stdout, "Password-Change-Error: " + "%s\n.\n", error_string); + } else { + x_fprintf(x_stdout, "Password-Change: Yes\n"); + } + + SAFE_FREE(error_string); + } + /* clear out the state */ + new_nt_pswd = data_blob(NULL, 0); + old_nt_hash_enc = data_blob(NULL, 0); + new_lm_pswd = data_blob(NULL, 0); + old_nt_hash_enc = data_blob(NULL, 0); + SAFE_FREE(full_username); + SAFE_FREE(username); + SAFE_FREE(domain); + SAFE_FREE(newpswd); + SAFE_FREE(oldpswd); + x_fprintf(x_stdout, ".\n"); + + return; + } + + request = buf; + + /* Indicates a base64 encoded structure */ + parameter = strstr_m(request, ":: "); + if (!parameter) { + parameter = strstr_m(request, ": "); + + if (!parameter) { + DEBUG(0, ("Parameter not found!\n")); + x_fprintf(x_stdout, "Error: Parameter not found!\n.\n"); + return; + } + + parameter[0] ='\0'; + parameter++; + parameter[0] ='\0'; + parameter++; + } else { + parameter[0] ='\0'; + parameter++; + parameter[0] ='\0'; + parameter++; + parameter[0] ='\0'; + parameter++; + + base64_decode_inplace(parameter); + } + + if (strequal(request, "new-nt-password-blob")) { + new_nt_pswd = strhex_to_data_blob(NULL, parameter); + if (new_nt_pswd.length != 516) { + x_fprintf(x_stdout, "Error: hex decode of %s failed! " + "(got %d bytes, expected 516)\n.\n", + parameter, + (int)new_nt_pswd.length); + new_nt_pswd = data_blob(NULL, 0); + } + } else if (strequal(request, "old-nt-hash-blob")) { + old_nt_hash_enc = strhex_to_data_blob(NULL, parameter); + if (old_nt_hash_enc.length != 16) { + x_fprintf(x_stdout, "Error: hex decode of %s failed! " + "(got %d bytes, expected 16)\n.\n", + parameter, + (int)old_nt_hash_enc.length); + old_nt_hash_enc = data_blob(NULL, 0); + } + } else if (strequal(request, "new-lm-password-blob")) { + new_lm_pswd = strhex_to_data_blob(NULL, parameter); + if (new_lm_pswd.length != 516) { + x_fprintf(x_stdout, "Error: hex decode of %s failed! " + "(got %d bytes, expected 516)\n.\n", + parameter, + (int)new_lm_pswd.length); + new_lm_pswd = data_blob(NULL, 0); + } + } + else if (strequal(request, "old-lm-hash-blob")) { + old_lm_hash_enc = strhex_to_data_blob(NULL, parameter); + if (old_lm_hash_enc.length != 16) + { + x_fprintf(x_stdout, "Error: hex decode of %s failed! " + "(got %d bytes, expected 16)\n.\n", + parameter, + (int)old_lm_hash_enc.length); + old_lm_hash_enc = data_blob(NULL, 0); + } + } else if (strequal(request, "nt-domain")) { + domain = smb_xstrdup(parameter); + } else if(strequal(request, "username")) { + username = smb_xstrdup(parameter); + } else if(strequal(request, "full-username")) { + username = smb_xstrdup(parameter); + } else if(strequal(request, "new-password")) { + newpswd = smb_xstrdup(parameter); + } else if (strequal(request, "old-password")) { + oldpswd = smb_xstrdup(parameter); + } else { + x_fprintf(x_stdout, "Error: Unknown request %s\n.\n", request); + } +} + static void manage_squid_request(enum stdio_helper_mode helper_mode, stdio_helper_function fn) { char buf[SQUID_BUFFER_SIZE+1]; -- cgit