X-Git-Url: http://git.samba.org/samba.git/?p=tprouty%2Fsamba.git;a=blobdiff_plain;f=source%2Fsmbd%2Fchgpasswd.c;h=1227163c224d6ac64c3498982aaf37d98fc3e606;hp=1c31d9757a1f75e033c6c51721012a2e38756093;hb=436555f05ceae34d8df2356d1066b6b5e0a07c41;hpb=5ff5f540ccc2ee2c9859213b84598baa400f57a5 diff --git a/source/smbd/chgpasswd.c b/source/smbd/chgpasswd.c index 1c31d9757a..1227163c22 100644 --- a/source/smbd/chgpasswd.c +++ b/source/smbd/chgpasswd.c @@ -2,7 +2,7 @@ Unix SMB/CIFS implementation. Samba utility functions Copyright (C) Andrew Tridgell 1992-1998 - Copyright (C) Andrew Bartlett 2001-2002 + Copyright (C) Andrew Bartlett 2001-2004 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 @@ -19,6 +19,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +/* These comments regard the code to change the user's unix password: */ + /* fork a child process to exec passwd and write to its * tty to change a users password. This is running as the * user who is attempting to change the password. @@ -30,9 +32,6 @@ * was included as a client to change passwords using the 'passwd' program * on the remote machine. * - * This routine is called by set_user_password() in password.c only if ALLOW_PASSWORD_CHANGE - * is defined in the compiler directives located in the Makefile. - * * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences * and rights to modify, distribute or incorporate this change to the CAP suite or @@ -52,10 +51,12 @@ extern struct passdb_ops pdb_ops; static NTSTATUS check_oem_password(const char *user, - uchar * lmdata, const uchar * lmhash, - const uchar * ntdata, const uchar * nthash, - SAM_ACCOUNT **hnd, char *new_passwd, - int new_passwd_size); + uchar password_encrypted_with_lm_hash[516], + const uchar old_lm_hash_encrypted[16], + uchar password_encrypted_with_nt_hash[516], + const uchar old_nt_hash_encrypted[16], + struct samu **hnd, char *new_passwd, + int new_passwd_size); #if ALLOW_CHANGE_PASSWORD @@ -63,8 +64,8 @@ static int findpty(char **slave) { int master; static fstring line; - DIR *dirp; - char *dpname; + SMB_STRUCT_DIR *dirp; + const char *dpname; #if defined(HAVE_GRANTPT) /* Try to open /dev/ptmx. If that fails, fall through to old method. */ @@ -92,7 +93,7 @@ static int findpty(char **slave) fstrcpy(line, "/dev/ptyXX"); - dirp = opendir("/dev"); + dirp = sys_opendir("/dev"); if (!dirp) return (-1); while ((dpname = readdirname(dirp)) != NULL) @@ -109,12 +110,12 @@ static int findpty(char **slave) DEBUG(3, ("pty: opened %s\n", line)); line[5] = 't'; *slave = line; - closedir(dirp); + sys_closedir(dirp); return (master); } } } - closedir(dirp); + sys_closedir(dirp); return (-1); } @@ -152,9 +153,13 @@ static int dochild(int master, const char *slavedev, const struct passwd *pass, DEBUG(3, ("More weirdness, could not open %s\n", slavedev)); return (False); } -#ifdef I_PUSH - ioctl(slave, I_PUSH, "ptem"); - ioctl(slave, I_PUSH, "ldterm"); +#if defined(I_PUSH) && defined(I_FIND) + if (ioctl(slave, I_FIND, "ptem") == 0) { + ioctl(slave, I_PUSH, "ptem"); + } + if (ioctl(slave, I_FIND, "ldterm") == 0) { + ioctl(slave, I_PUSH, "ldterm"); + } #elif defined(TIOCSCTTY) if (ioctl(slave, TIOCSCTTY, 0) < 0) { @@ -236,7 +241,7 @@ static int expect(int master, char *issue, char *expected) if (lp_passwd_chat_debug()) DEBUG(100, ("expect: sending [%s]\n", issue)); - if ((len = write(master, issue, strlen(issue))) != strlen(issue)) { + if ((len = sys_write(master, issue, strlen(issue))) != strlen(issue)) { DEBUG(2,("expect: (short) write returned %d\n", len )); return False; } @@ -245,7 +250,8 @@ static int expect(int master, char *issue, char *expected) if (strequal(expected, ".")) return True; - timeout = 2000; + /* Initial timeout. */ + timeout = lp_passwd_chat_timeout() * 1000; nread = 0; buffer[nread] = 0; @@ -259,10 +265,12 @@ static int expect(int master, char *issue, char *expected) /* Eat leading/trailing whitespace before match. */ pstring str; pstrcpy( str, buffer); - trim_string( str, " ", " "); + trim_char( str, ' ', ' '); - if ((match = (unix_wild_match(expected, str) == 0))) - timeout = 200; + if ((match = unix_wild_match(expected, str)) == True) { + /* Now data has started to return, lower timeout. */ + timeout = lp_passwd_chat_timeout() * 100; + } } } @@ -324,7 +332,7 @@ static int talktochild(int master, const char *seq) return (count > 0); } -static BOOL chat_with_program(char *passwordprogram, struct passwd *pass, +static BOOL chat_with_program(char *passwordprogram, const struct passwd *pass, char *chatsequence, BOOL as_root) { char *slavedev; @@ -333,19 +341,14 @@ static BOOL chat_with_program(char *passwordprogram, struct passwd *pass, int wstat; BOOL chstat = False; - if (pass == NULL) - { - DEBUG(0, - ("chat_with_program: user doesn't exist in the UNIX password database.\n")); + if (pass == NULL) { + DEBUG(0, ("chat_with_program: user doesn't exist in the UNIX password database.\n")); return False; } /* allocate a pseudo-terminal device */ - if ((master = findpty(&slavedev)) < 0) - { - DEBUG(3, - ("Cannot Allocate pty for password change: %s\n", - pass->pw_name)); + if ((master = findpty(&slavedev)) < 0) { + DEBUG(3, ("chat_with_program: Cannot Allocate pty for password change: %s\n", pass->pw_name)); return (False); } @@ -356,40 +359,30 @@ static BOOL chat_with_program(char *passwordprogram, struct passwd *pass, CatchChildLeaveStatus(); - if ((pid = sys_fork()) < 0) - { - DEBUG(3, - ("Cannot fork() child for password change: %s\n", - pass->pw_name)); + if ((pid = sys_fork()) < 0) { + DEBUG(3, ("chat_with_program: Cannot fork() child for password change: %s\n", pass->pw_name)); close(master); CatchChild(); return (False); } /* we now have a pty */ - if (pid > 0) - { /* This is the parent process */ - if ((chstat = talktochild(master, chatsequence)) == False) - { - DEBUG(3, - ("Child failed to change password: %s\n", - pass->pw_name)); + if (pid > 0) { /* This is the parent process */ + if ((chstat = talktochild(master, chatsequence)) == False) { + DEBUG(3, ("chat_with_program: Child failed to change password: %s\n", pass->pw_name)); kill(pid, SIGKILL); /* be sure to end this process */ } - while ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) - { - if (errno == EINTR) - { + while ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) { + if (errno == EINTR) { errno = 0; continue; } break; } - if (wpid < 0) - { - DEBUG(3, ("The process is no longer waiting!\n\n")); + if (wpid < 0) { + DEBUG(3, ("chat_with_program: The process is no longer waiting!\n\n")); close(master); CatchChild(); return (False); @@ -402,35 +395,30 @@ static BOOL chat_with_program(char *passwordprogram, struct passwd *pass, close(master); - if (pid != wpid) - { - DEBUG(3, - ("We were waiting for the wrong process ID\n")); + if (pid != wpid) { + DEBUG(3, ("chat_with_program: We were waiting for the wrong process ID\n")); return (False); } - if (WIFEXITED(wstat) == 0) - { - DEBUG(3, - ("The process exited while we were waiting\n")); + if (WIFEXITED(wstat) && (WEXITSTATUS(wstat) != 0)) { + DEBUG(3, ("chat_with_program: The process exited with status %d \ +while we were waiting\n", WEXITSTATUS(wstat))); return (False); } - if (WEXITSTATUS(wstat) != 0) - { - DEBUG(3, - ("The status of the process exiting was %d\n", - wstat)); +#if defined(WIFSIGNALLED) && defined(WTERMSIG) + else if (WIFSIGNALLED(wstat)) { + DEBUG(3, ("chat_with_program: The process was killed by signal %d \ +while we were waiting\n", WTERMSIG(wstat))); return (False); } - - } - else - { +#endif + } else { /* CHILD */ /* - * Lose any oplock capabilities. + * Lose any elevated privileges. */ - oplock_set_capability(False, False); + drop_effective_capability(KERNEL_OPLOCK_CAPABILITY); + drop_effective_capability(DMAPI_ACCESS_CAPABILITY); /* make sure it doesn't freeze */ alarm(20); @@ -438,12 +426,9 @@ static BOOL chat_with_program(char *passwordprogram, struct passwd *pass, if (as_root) become_root(); - DEBUG(3, - ("Dochild for user %s (uid=%d,gid=%d)\n", pass->pw_name, - (int)getuid(), (int)getgid())); - chstat = - dochild(master, slavedev, pass, passwordprogram, - as_root); + DEBUG(3, ("chat_with_program: Dochild for user %s (uid=%d,gid=%d) (as_root = %s)\n", pass->pw_name, + (int)getuid(), (int)getgid(), BOOLSTR(as_root) )); + chstat = dochild(master, slavedev, pass, passwordprogram, as_root); if (as_root) unbecome_root(); @@ -452,56 +437,40 @@ static BOOL chat_with_program(char *passwordprogram, struct passwd *pass, * The child should never return from dochild() .... */ - DEBUG(0, - ("chat_with_program: Error: dochild() returned %d\n", - chstat)); + DEBUG(0, ("chat_with_program: Error: dochild() returned %d\n", chstat)); exit(1); } if (chstat) - DEBUG(3, - ("Password change %ssuccessful for user %s\n", + DEBUG(3, ("chat_with_program: Password change %ssuccessful for user %s\n", (chstat ? "" : "un"), pass->pw_name)); return (chstat); } - -BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root) +BOOL chgpasswd(const char *name, const struct passwd *pass, + const char *oldpass, const char *newpass, BOOL as_root) { pstring passwordprogram; pstring chatsequence; size_t i; size_t len; - struct passwd *pass; - - if (!name) { - DEBUG(1, ("NULL username specfied to chgpasswd()!\n")); - } - if (!oldpass) { oldpass = ""; } - DEBUG(3, ("Password change for user: %s\n", name)); + DEBUG(3, ("chgpasswd: Password change (as_root=%s) for user: %s\n", BOOLSTR(as_root), name)); -#if DEBUG_PASSWORD - DEBUG(100, ("Passwords: old=%s new=%s\n", oldpass, newpass)); +#ifdef DEBUG_PASSWORD + DEBUG(100, ("chgpasswd: Passwords: old=%s new=%s\n", oldpass, newpass)); #endif /* Take the passed information and test it for minimum criteria */ - /* Minimum password length */ - if (strlen(newpass) < lp_min_passwd_length()) { - /* too short, must be at least MINPASSWDLENGTH */ - DEBUG(0, ("Password Change: user %s, New password is shorter than minimum password length = %d\n", - name, lp_min_passwd_length())); - return (False); /* inform the user */ - } /* Password is same as old password */ if (strcmp(oldpass, newpass) == 0) { /* don't allow same password */ - DEBUG(2, ("Password Change: %s, New password is same as old\n", name)); /* log the attempt */ + DEBUG(2, ("chgpasswd: Password Change: %s, New password is same as old\n", name)); /* log the attempt */ return (False); /* inform the user */ } @@ -513,8 +482,7 @@ BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL len = strlen(oldpass); for (i = 0; i < len; i++) { if (iscntrl((int)oldpass[i])) { - DEBUG(0, - ("chat_with_program: oldpass contains control characters (disallowed).\n")); + DEBUG(0, ("chgpasswd: oldpass contains control characters (disallowed).\n")); return False; } } @@ -522,14 +490,11 @@ BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL len = strlen(newpass); for (i = 0; i < len; i++) { if (iscntrl((int)newpass[i])) { - DEBUG(0, - ("chat_with_program: newpass contains control characters (disallowed).\n")); + DEBUG(0, ("chgpasswd: newpass contains control characters (disallowed).\n")); return False; } } - pass = Get_Pwnam(name); - #ifdef WITH_PAM if (lp_pam_password_change()) { BOOL ret; @@ -552,11 +517,8 @@ BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL /* A non-PAM password change just doen't make sense without a valid local user */ - if (pass == NULL) - { - DEBUG(0, - ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", - name)); + if (pass == NULL) { + DEBUG(0, ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", name)); return False; } @@ -575,7 +537,7 @@ BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL if (as_root) { /* The password program *must* contain the user name to work. Fail if not. */ - if (strstr(passwordprogram, "%u") == NULL) { + if (strstr_m(passwordprogram, "%u") == NULL) { DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \ the string %%u, and the given string %s does not.\n", passwordprogram )); return False; @@ -596,9 +558,10 @@ the string %%u, and the given string %s does not.\n", passwordprogram )); #else /* ALLOW_CHANGE_PASSWORD */ -BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root) +BOOL chgpasswd(const char *name, const struct passwd *pass, + const char *oldpass, const char *newpass, BOOL as_root) { - DEBUG(0, ("Password changing not compiled in (user=%s)\n", name)); + DEBUG(0, ("chgpasswd: Unix Password changing not compiled in (user=%s)\n", name)); return (False); } #endif /* ALLOW_CHANGE_PASSWORD */ @@ -608,14 +571,19 @@ BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL ************************************************************/ BOOL check_lanman_password(char *user, uchar * pass1, - uchar * pass2, SAM_ACCOUNT **hnd) + uchar * pass2, struct samu **hnd) { uchar unenc_new_pw[16]; uchar unenc_old_pw[16]; - SAM_ACCOUNT *sampass = NULL; - uint16 acct_ctrl; + struct samu *sampass = NULL; + uint32 acct_ctrl; const uint8 *lanman_pw; BOOL ret; + + if ( !(sampass = samu_new(NULL)) ) { + DEBUG(0, ("samu_new() failed!\n")); + return False; + } become_root(); ret = pdb_getsampwnam(sampass, user); @@ -623,7 +591,7 @@ BOOL check_lanman_password(char *user, uchar * pass1, if (ret == False) { DEBUG(0,("check_lanman_password: getsampwnam returned NULL\n")); - pdb_free_sam(&sampass); + TALLOC_FREE(sampass); return False; } @@ -632,7 +600,7 @@ BOOL check_lanman_password(char *user, uchar * pass1, if (acct_ctrl & ACB_DISABLED) { DEBUG(0,("check_lanman_password: account %s disabled.\n", user)); - pdb_free_sam(&sampass); + TALLOC_FREE(sampass); return False; } @@ -643,7 +611,7 @@ BOOL check_lanman_password(char *user, uchar * pass1, return True; } else { DEBUG(0, ("check_lanman_password: no lanman password !\n")); - pdb_free_sam(&sampass); + TALLOC_FREE(sampass); return False; } } @@ -657,7 +625,7 @@ BOOL check_lanman_password(char *user, uchar * pass1, /* Check that the two old passwords match. */ if (memcmp(lanman_pw, unenc_old_pw, 16)) { DEBUG(0,("check_lanman_password: old password doesn't match.\n")); - pdb_free_sam(&sampass); + TALLOC_FREE(sampass); return False; } @@ -670,14 +638,16 @@ BOOL check_lanman_password(char *user, uchar * pass1, Code to change the lanman hashed password. It nulls out the NT hashed password as it will no longer be valid. + NOTE this function is designed to be called as root. Check the old password + is correct before calling. JRA. ************************************************************/ -BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar *pass2) +BOOL change_lanman_password(struct samu *sampass, uchar *pass2) { static uchar null_pw[16]; uchar unenc_new_pw[16]; BOOL ret; - uint16 acct_ctrl; + uint32 acct_ctrl; const uint8 *pwd; if (sampass == NULL) { @@ -719,16 +689,14 @@ BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar *pass2) return False; /* We lose the NT hash. Sorry. */ } - if (!pdb_set_pass_changed_now (sampass)) { - pdb_free_sam(&sampass); + if (!pdb_set_pass_last_set_time (sampass, time(NULL), PDB_CHANGED)) { + TALLOC_FREE(sampass); /* Not quite sure what this one qualifies as, but this will do */ return False; } /* Now flush the sam_passwd struct to persistent storage */ - become_root(); - ret = pdb_update_sam_account (sampass); - unbecome_root(); + ret = NT_STATUS_IS_OK(pdb_update_sam_account (sampass)); return ret; } @@ -736,72 +704,79 @@ BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar *pass2) /*********************************************************** Code to check and change the OEM hashed password. ************************************************************/ + NTSTATUS pass_oem_change(char *user, - uchar * lmdata, uchar * lmhash, - uchar * ntdata, uchar * nthash) + uchar password_encrypted_with_lm_hash[516], + const uchar old_lm_hash_encrypted[16], + uchar password_encrypted_with_nt_hash[516], + const uchar old_nt_hash_encrypted[16], + uint32 *reject_reason) { - fstring new_passwd; - const char *unix_user; - SAM_ACCOUNT *sampass = NULL; - NTSTATUS nt_status - = check_oem_password(user, lmdata, lmhash, ntdata, nthash, - &sampass, new_passwd, sizeof(new_passwd)); - + pstring new_passwd; + struct samu *sampass = NULL; + NTSTATUS nt_status = check_oem_password(user, password_encrypted_with_lm_hash, + old_lm_hash_encrypted, + password_encrypted_with_nt_hash, + old_nt_hash_encrypted, + &sampass, new_passwd, sizeof(new_passwd)); + if (!NT_STATUS_IS_OK(nt_status)) return nt_status; - /* - * At this point we have the new case-sensitive plaintext - * password in the fstring new_passwd. If we wanted to synchronise - * with UNIX passwords we would call a UNIX password changing - * function here. However it would have to be done as root - * as the plaintext of the old users password is not - * available. JRA. - */ - - unix_user = pdb_get_username(sampass); - - nt_status = change_oem_password(sampass, NULL, new_passwd); + /* We've already checked the old password here.... */ + become_root(); + nt_status = change_oem_password(sampass, NULL, new_passwd, True, reject_reason); + unbecome_root(); memset(new_passwd, 0, sizeof(new_passwd)); - pdb_free_sam(&sampass); + TALLOC_FREE(sampass); return nt_status; } /*********************************************************** - Code to check the OEM hashed password. + Decrypt and verify a user password change. - this function ignores the 516 byte nt OEM hashed password - but does use the lm OEM password to check the nt hashed-hash. + The 516 byte long buffers are encrypted with the old NT and + old LM passwords, and if the NT passwords are present, both + buffers contain a unicode string. + After decrypting the buffers, check the password is correct by + matching the old hashed passwords with the passwords in the passdb. + ************************************************************/ static NTSTATUS check_oem_password(const char *user, - uchar * lmdata, const uchar * lmhash, - const uchar * ntdata, const uchar * nthash, - SAM_ACCOUNT **hnd, char *new_passwd, - int new_passwd_size) + uchar password_encrypted_with_lm_hash[516], + const uchar old_lm_hash_encrypted[16], + uchar password_encrypted_with_nt_hash[516], + const uchar old_nt_hash_encrypted[16], + struct samu **hnd, char *new_passwd, + int new_passwd_size) { static uchar null_pw[16]; static uchar null_ntpw[16]; - SAM_ACCOUNT *sampass = NULL; + struct samu *sampass = NULL; + uint8 *password_encrypted; + const uint8 *encryption_key; const uint8 *lanman_pw, *nt_pw; - uint16 acct_ctrl; - int new_pw_len; - uchar new_ntp16[16]; - uchar unenc_old_ntpw[16]; - uchar new_p16[16]; - uchar unenc_old_pw[16]; + uint32 acct_ctrl; + uint32 new_pw_len; + uchar new_nt_hash[16]; + uchar new_lm_hash[16]; + uchar verifier[16]; char no_pw[2]; BOOL ret; - BOOL nt_pass_set = (ntdata != NULL && nthash != NULL); + BOOL nt_pass_set = (password_encrypted_with_nt_hash && old_nt_hash_encrypted); + BOOL lm_pass_set = (password_encrypted_with_lm_hash && old_lm_hash_encrypted); *hnd = NULL; - pdb_init_sam(&sampass); + if ( !(sampass = samu_new( NULL )) ) { + return NT_STATUS_NO_MEMORY; + } become_root(); ret = pdb_getsampwnam(sampass, user); @@ -809,101 +784,152 @@ static NTSTATUS check_oem_password(const char *user, if (ret == False) { DEBUG(0, ("check_oem_password: getsmbpwnam returned NULL\n")); - pdb_free_sam(&sampass); - return NT_STATUS_WRONG_PASSWORD; - /* - TODO: check what Win2k returns for this: - return NT_STATUS_NO_SUCH_USER; - */ + TALLOC_FREE(sampass); + return NT_STATUS_NO_SUCH_USER; } acct_ctrl = pdb_get_acct_ctrl(sampass); if (acct_ctrl & ACB_DISABLED) { - DEBUG(0,("check_lanman_password: account %s disabled.\n", user)); - pdb_free_sam(&sampass); + DEBUG(2,("check_lanman_password: account %s disabled.\n", user)); + TALLOC_FREE(sampass); return NT_STATUS_ACCOUNT_DISABLED; } - /* construct a null password (in case one is needed */ - no_pw[0] = 0; - no_pw[1] = 0; - nt_lm_owf_gen(no_pw, null_ntpw, null_pw); + if ((acct_ctrl & ACB_PWNOTREQ) && lp_null_passwords()) { + /* construct a null password (in case one is needed */ + no_pw[0] = 0; + no_pw[1] = 0; + nt_lm_owf_gen(no_pw, null_ntpw, null_pw); + lanman_pw = null_pw; + nt_pw = null_pw; - /* save pointers to passwords so we don't have to keep looking them up */ - lanman_pw = pdb_get_lanman_passwd(sampass); - nt_pw = pdb_get_nt_passwd(sampass); - - /* check for null passwords */ - if (lanman_pw == NULL) { - if (!(acct_ctrl & ACB_PWNOTREQ)) { - DEBUG(0,("check_oem_password: no lanman password !\n")); - pdb_free_sam(&sampass); - return NT_STATUS_WRONG_PASSWORD; + } else { + /* save pointers to passwords so we don't have to keep looking them up */ + if (lp_lanman_auth()) { + lanman_pw = pdb_get_lanman_passwd(sampass); + } else { + lanman_pw = NULL; } + nt_pw = pdb_get_nt_passwd(sampass); } - - if (pdb_get_nt_passwd(sampass) == NULL && nt_pass_set) { - if (!(acct_ctrl & ACB_PWNOTREQ)) { - DEBUG(0,("check_oem_password: no ntlm password !\n")); - pdb_free_sam(&sampass); - return NT_STATUS_WRONG_PASSWORD; + + if (nt_pw && nt_pass_set) { + /* IDEAL Case: passwords are in unicode, and we can + * read use the password encrypted with the NT hash + */ + password_encrypted = password_encrypted_with_nt_hash; + encryption_key = nt_pw; + } else if (lanman_pw && lm_pass_set) { + /* password may still be in unicode, but use LM hash version */ + password_encrypted = password_encrypted_with_lm_hash; + encryption_key = lanman_pw; + } else if (nt_pass_set) { + DEBUG(1, ("NT password change supplied for user %s, but we have no NT password to check it with\n", + user)); + TALLOC_FREE(sampass); + return NT_STATUS_WRONG_PASSWORD; + } else if (lm_pass_set) { + if (lp_lanman_auth()) { + DEBUG(1, ("LM password change supplied for user %s, but we have no LanMan password to check it with\n", + user)); + } else { + DEBUG(1, ("LM password change supplied for user %s, but we have disabled LanMan authentication\n", + user)); } + TALLOC_FREE(sampass); + return NT_STATUS_WRONG_PASSWORD; + } else { + DEBUG(1, ("password change requested for user %s, but no password supplied!\n", + user)); + TALLOC_FREE(sampass); + return NT_STATUS_WRONG_PASSWORD; } - - /* - * Call the hash function to get the new password. - */ - SamOEMhash( lmdata, lanman_pw, 516); /* - * The length of the new password is in the last 4 bytes of - * the data buffer. + * Decrypt the password with the key */ + SamOEMhash( password_encrypted, encryption_key, 516); - new_pw_len = IVAL(lmdata, 512); - - DEBUG(0, ("** new_pw_len = %d, new_passwd_size = %d\n", - new_pw_len, new_passwd_size)); - - if (new_pw_len < 0 || new_pw_len > new_passwd_size - 1) { - DEBUG(0,("check_oem_password: incorrect password length (%d).\n", new_pw_len)); - pdb_free_sam(&sampass); + if ( !decode_pw_buffer(password_encrypted, new_passwd, new_passwd_size, &new_pw_len, + nt_pass_set ? STR_UNICODE : STR_ASCII)) { + TALLOC_FREE(sampass); return NT_STATUS_WRONG_PASSWORD; } - if (nt_pass_set) { - /* - * nt passwords are in unicode - */ - pull_ucs2(NULL, new_passwd, - (const smb_ucs2_t *)&lmdata[512 - new_pw_len], - new_passwd_size, new_pw_len, 0); - } else { - memcpy(new_passwd, &lmdata[512 - new_pw_len], new_pw_len); - new_passwd[new_pw_len] = 0; - } - /* * To ensure we got the correct new password, hash it and * use it as a key to test the passed old password. */ - nt_lm_owf_gen(new_passwd, new_ntp16, new_p16); + if (nt_pass_set) { + /* NT passwords, verify the NT hash. */ + + /* Calculate the MD4 hash (NT compatible) of the password */ + memset(new_nt_hash, '\0', 16); + E_md4hash(new_passwd, new_nt_hash); + + if (nt_pw) { + /* + * check the NT verifier + */ + E_old_pw_hash(new_nt_hash, nt_pw, verifier); + if (memcmp(verifier, old_nt_hash_encrypted, 16)) { + DEBUG(0,("check_oem_password: old lm password doesn't match.\n")); + TALLOC_FREE(sampass); + return NT_STATUS_WRONG_PASSWORD; + } + + /* We could check the LM password here, but there is + * little point, we already know the password is + * correct, and the LM password might not even be + * present. */ + + /* Further, LM hash generation algorithms + * differ with charset, so we could + * incorrectly fail a perfectly valid password + * change */ +#ifdef DEBUG_PASSWORD + DEBUG(100, + ("check_oem_password: password %s ok\n", new_passwd)); +#endif + *hnd = sampass; + return NT_STATUS_OK; + } + + if (lanman_pw) { + /* + * check the lm verifier + */ + E_old_pw_hash(new_nt_hash, lanman_pw, verifier); + if (memcmp(verifier, old_lm_hash_encrypted, 16)) { + DEBUG(0,("check_oem_password: old lm password doesn't match.\n")); + TALLOC_FREE(sampass); + return NT_STATUS_WRONG_PASSWORD; + } +#ifdef DEBUG_PASSWORD + DEBUG(100, + ("check_oem_password: password %s ok\n", new_passwd)); +#endif + *hnd = sampass; + return NT_STATUS_OK; + } + } + + if (lanman_pw && lm_pass_set) { + + E_deshash(new_passwd, new_lm_hash); - if (!nt_pass_set) { /* - * Now use new_p16 as the key to see if the old - * password matches. + * check the lm verifier */ - D_P16(new_p16, lmhash, unenc_old_pw); - - if (memcmp(lanman_pw, unenc_old_pw, 16)) { + E_old_pw_hash(new_lm_hash, lanman_pw, verifier); + if (memcmp(verifier, old_lm_hash_encrypted, 16)) { DEBUG(0,("check_oem_password: old lm password doesn't match.\n")); - pdb_free_sam(&sampass); + TALLOC_FREE(sampass); return NT_STATUS_WRONG_PASSWORD; } - + #ifdef DEBUG_PASSWORD DEBUG(100, ("check_oem_password: password %s ok\n", new_passwd)); @@ -912,83 +938,186 @@ static NTSTATUS check_oem_password(const char *user, return NT_STATUS_OK; } - /* - * Now use new_p16 as the key to see if the old - * password matches. - */ - D_P16(new_ntp16, lmhash, unenc_old_pw); - D_P16(new_ntp16, nthash, unenc_old_ntpw); + /* should not be reached */ + TALLOC_FREE(sampass); + return NT_STATUS_WRONG_PASSWORD; +} - if (memcmp(lanman_pw, unenc_old_pw, 16)) { - DEBUG(0,("check_oem_password: old lm password doesn't match.\n")); - pdb_free_sam(&sampass); - return NT_STATUS_WRONG_PASSWORD; +/*********************************************************** + This routine takes the given password and checks it against + the password history. Returns True if this password has been + found in the history list. +************************************************************/ + +static BOOL check_passwd_history(struct samu *sampass, const char *plaintext) +{ + uchar new_nt_p16[NT_HASH_LEN]; + uchar zero_md5_nt_pw[SALTED_MD5_HASH_LEN]; + const uint8 *nt_pw; + const uint8 *pwhistory; + BOOL found = False; + int i; + uint32 pwHisLen, curr_pwHisLen; + + pdb_get_account_policy(AP_PASSWORD_HISTORY, &pwHisLen); + if (pwHisLen == 0) { + return False; } - if (memcmp(nt_pw, unenc_old_ntpw, 16)) { - DEBUG(0,("check_oem_password: old nt password doesn't match.\n")); - pdb_free_sam(&sampass); - return NT_STATUS_WRONG_PASSWORD; + pwhistory = pdb_get_pw_history(sampass, &curr_pwHisLen); + if (!pwhistory || curr_pwHisLen == 0) { + return False; } -#ifdef DEBUG_PASSWORD - DEBUG(100, ("check_oem_password: password %s ok\n", new_passwd)); -#endif - *hnd = sampass; - return NT_STATUS_OK; + /* Only examine the minimum of the current history len and + the stored history len. Avoids race conditions. */ + pwHisLen = MIN(pwHisLen,curr_pwHisLen); + + nt_pw = pdb_get_nt_passwd(sampass); + + E_md4hash(plaintext, new_nt_p16); + + if (!memcmp(nt_pw, new_nt_p16, NT_HASH_LEN)) { + DEBUG(10,("check_passwd_history: proposed new password for user %s is the same as the current password !\n", + pdb_get_username(sampass) )); + return True; + } + + dump_data(100, new_nt_p16, NT_HASH_LEN); + dump_data(100, pwhistory, PW_HISTORY_ENTRY_LEN*pwHisLen); + + memset(zero_md5_nt_pw, '\0', SALTED_MD5_HASH_LEN); + for (i=0; i