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