Password change code
Patrick McCarty
mccartyp at apu.edu
Mon Jul 15 00:11:02 GMT 2002
> Well, you can't change that function - becouse it is used by other
> code. But you can write another (better) wrapper.
>
> > How far off base am I?
>
> Well, the LM password change code doesn't set the plaintext, the two
> 'main' password change mechisms are as described. The SAMR calls wraps
> the lanman.c RAP call. That is, they both take a 'blob' containing the
> obscured old and new passwords (in a sort of challange-response kind of
> format). These get authenticated by the check_oem_password() code,
> which returns the new, plaintext password.
>
> The oddball call is api_SetUserPassword() in lanman.c, which can get
> either a plaintext old and new password (win98, authenticated via the
> normal code paths) or a LM hash only.
So then, after the passwords are validated by check_oem_password, we can
pass the plaintext passwords to this function instead of
pdb_set_plaintext_password?
Since this code will take plaintext or hashed (depending on how you setup
the bitmasks it is passed) it is fairly versatile.
Is that the direction you had in mind?
> Looking at the code:
>
> Firstly, when I say that the incoming password is an LM hash, it means
> that it is already hashed.
Fixed now.
> Also, if you change only the LM hash, you should invalidate (set to
> NULL) the NT hash.
I believe setting it to NULL will cause issues with some backends such as
LDAP. I now invalidate it by setting it to all X's, much like I saw done
somewhere else in the code.
> If the password is too short, return NT_STATUS_PASSWORD_RESTRICTION, or
> whatever NT returns for that error. This ensures we get the right error
> message on the client.
I'll need to look at these more closely when it this gets integrated.
There are a couple that I know need to be changed for sure.
> Finally, put the 'unix password sync' code inside this function, but
> don't call it for machine accounts.
K, did that now.
> Do this bit inside its own 'wrapper' function, that the RPC-based
> adminstrative password changes can also use.
Im not sure what you mean here.. You mean put the password change stuff in
a seperate function yet? I'm confused... You can turn off Unix sync by
flipping the appropriate bit in my newly defined PCB (password control
bit) bitmask.
A bitmask was the only way I could think of sending multiple sendings to
the function, if you know of a better way, i'm all for it.
> We should test if machines are premitted to change their password this
> way at all. (They *should* make a netlogon call).
I'm guessing by this that there is some additional structure I should be
checking something against?
> Did I mention this code is complex?
=) Complex enough to expose my relative inexperience with samba internals
and with C in general.
Guess its a good thing I dont scare easily.
Tomorrow I'll give it a go at trying to integrate and test this code. I'm
SURE i'll find some problems. I haven't even tried to compile this yet, I
simply wrote it up in VIM =) But I think im pretty close nevertheless.
--
Patrick McCarty
Video Technician
Azusa Pacific University
Logic is a systematic method of coming to the wrong conclusion with confidence.
-------------- next part --------------
NTSTATUS master_change_password(const char *user_name, const char *old_passwd, const char *new_passwd, int *pcb_flags)
{
SAM_ACCOUNT *sam_pass=NULL;
uint16 acct_ctrl;
uint32 min_pass_len;
uchar new_lanman_p16[16];
uchar new_nt_p16[16];
fstring no_pass;
BOOL ret;
pstring passwordprogram;
pstring chatsequence;
size_t i;
size_t len;
struct passwd *pass;
const time_t now = time(NULL);
/* Validate existence of arguments */
if (!user_name || !new_passwd || !pcb_flags)
return NT_STATUS_UNSUCCESSFUL;
DEBUG(3, ("Password change for user: %s\n", name));
#if DEBUG_PASSWORD
DEBUG(100, ("Passwords: old=%s new=%s\n", oldpass, newpass));
#endif
/* Get the smb passwd entry for this user */
pdb_init_sam(&sam_pass);
if(!pdb_getsampwnam(sam_pass, user_name)) {
pdb_free_sam(&sam_pass);
return NT_STATUS_NO_SUCH_USER;
}
acct_ctrl = pdb_get_acct_ctrl(sampass);
/* Check to see if the account is disabled */
if (acct_ctrl & ACB_DISABLED) {
DEBUG(0,("master_change_password: account %s disabled.\n",
pdb_get_username(sampass)));
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
/* Check to see if user's password is locked */
if (acct_ctrl & ACB_PWLOCK) {
DEBUG(0,("master_change_password: account %s password locked.\n",
pdb_get_username(sampass)));
pdb_free_sam(&sam_pass);
return NT_STATUS_ACCESS_DENIED;
}
/* Check for pass_can_change_time here */
if (now <= pdb_get_pass_can_change_time(sampass)) {
DEBUG(0,("master_change_password: account %s pass_can_change_time not yet reached.\n",
pdb_get_username(sampass)));
pdb_free_sam(&sam_pass);
/* Im not sure which NT_STATUS this should be */
return NT_STATUS_UNSUCCESSFUL;
}
/* Check to see if we were passed a LANMAN hash only */
if (pcb_flags & PCB_LMHASH)
{
safe_strcpy(no_pass, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
/* Set the lanman to the hash we were passed */
if (!pdb_set_lanman_passwd (sampass, new_pass)) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
/* Invalidate the NT hash, since we just desynced the lanman */
if (!pdb_set_nt_passwd (sampass, no_pass)) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
/* Update the password set time */
if (!pdb_set_pass_changed_now (sampass)) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
become_root();
ret = pdb_update_sam_account(sampass);
unbecome_root();
if (!ret) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
DEBUG(0,("master_change_password: changed lanman password for: %s.\n",
pdb_get_username(sampass)));
pdb_free_sam(&sam_pass);
return NT_STATUS_OK;
}
/* If we get here, we have a plaintext password, so we can check length, etc */
/* Generate hashes */
nt_lm_owf_gen(new_passwd, new_nt_p16, new_lanman_p16);
/* Check minimum password length */
if (strlen(new_passwd) < lp_min_pass_length()) {
DEBUG(0,("master_change_password: New password for user %s shorter than min_passwd_length %d, not changed.\n",
user_name, lp_min_pass_length()));
pdb_free_sam(&sam_pass);
return NT_STATUS_PASSWORD_RESTRICTION;
}
/* TODO: Add cracklib check here */
/* Do UNIX password sync if we have the old password, configured to sync, and we are a normal user acct. */
if ((!pcb_flags & PCB_NOOLDPW) && (lp_unix_password_sync()) && (acct_ctrl & ACB_NORMAL) && (!pcb_flags & PCB_NOUNIXSYNC)) {
/* Check for identical new and old passwords */
if (strcmp(old_pass, new_pass) == 0) {
DEBUG(2, ("master_change_password: %s, New password is same as old\n", user_name)); /* log the attempt */
pdb_free_sam(&sam_pass);
return NT_STATUS_PASSWORD_RESTRICTION;
}
/* Check for control characters in the old password */
len = strlen(old_pass);
for (i = 0; i < len; i++) {
if (iscntrl((int)old_pass[i])) {
DEBUG(0,("chat_with_program: oldpass contains control characters (disallowed).\n"));
pdb_free_sam(&sam_pass);
return NT_STATUS_PASSWORD_RESTRICTION;
}
}
/* Check for control characters in the new password */
len = strlen(new_pass);
for (i = 0; i < len; i++) {
if (iscntrl((int)new_pass[i])) {
DEBUG(0, ("chat_with_program: newpass contains control characters (disallowed).\n"));
pdb_free_sam(&sam_pass);
return NT_STATUS_PASSWORD_RESTRICTION;
}
}
pass = Get_Pwnam(user_name);
#ifdef WITH_PAM
if (lp_pam_password_change()) {
become_root();
if (pass) {
ret = smb_pam_passchange(pass->pw_name, old_pass, new_pass);
} else {
ret = smb_pam_passchange(user_name, old_pass, new_pass);
}
unbecome_root();
if (!ret) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
}
#endif
if (pass == NULL)
{
DEBUG(0,
("master_change_password: user %s doesn't exist in the UNIX password database.\n",
pdb_free_sam(&sam_pass);
name));
return NT_STATUS_UNSUCCESSFUL;
}
pstrcpy(passwordprogram, lp_passwd_program());
pstrcpy(chatsequence, lp_passwd_chat());
if (!*chatsequence) {
DEBUG(2, ("master_change_password: Null chat sequence - no password changing\n"));
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
if (!*passwordprogram) {
DEBUG(2, ("master_change_password: Null password program - no password changing\n"));
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
/* The password program *must* contain the user name to work. Fail if not. */
if (strstr(passwordprogram, "%u") == NULL) {
DEBUG(0,("master_change_password: Running as root the 'passwd program' parameter *MUST* contain \
the string %%u, and the given string %s does not.\n", passwordprogram ));
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
pstring_sub(passwordprogram, "%u", name);
/* note that we do NOT substitute the %o and %n in the password program
* as this would open up a security hole where the user could use
* a new password containing shell escape characters */
pstring_sub(chatsequence, "%u", name);
all_string_sub(chatsequence, "%o", oldpass, sizeof(pstring));
all_string_sub(chatsequence, "%n", newpass, sizeof(pstring));
ret = chat_with_program(passwordprogram, pass, chatsequence, True);
if (!ret) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
}
if (!pdb_set_nt_passwd (sampass, new_nt_p16)) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
if (!pdb_set_lanman_passwd (sampass, new_lanman_p16)) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
if (!pdb_set_pass_changed_now (sampass)) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
become_root();
ret = pdb_update_sam_account(sampass);
unbecome_root();
if (!ret) {
pdb_free_sam(&sam_pass);
return NT_STATUS_UNSUCCESSFUL;
}
DEBUG(0,("master_change_password: changed password for: %s.\n",
pdb_get_username(sampass)));
pdb_free_sam(&sam_pass);
return NT_STATUS_OK;
}
More information about the samba-technical
mailing list