s3-passdb: Fix typo in comment.
[Samba.git] / source3 / rpc_server / srv_samr_chgpasswd.c
blob2e76e55dfa447ee95caedda906a90c0b7e77cc40
1 /*
2 Unix SMB/CIFS implementation.
3 Samba utility functions
4 Copyright (C) Andrew Tridgell 1992-1998
5 Copyright (C) Andrew Bartlett 2001-2004
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 /* These comments regard the code to change the user's unix password: */
23 /* fork a child process to exec passwd and write to its
24 * tty to change a users password. This is running as the
25 * user who is attempting to change the password.
29 * This code was copied/borrowed and stolen from various sources.
30 * The primary source was the poppasswd.c from the authors of POPMail. This software
31 * was included as a client to change passwords using the 'passwd' program
32 * on the remote machine.
34 * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson
35 * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences
36 * and rights to modify, distribute or incorporate this change to the CAP suite or
37 * using it for any other reason are granted, so long as this disclaimer is left intact.
41 This code was hacked considerably for inclusion in Samba, primarily
42 by Andrew.Tridgell@anu.edu.au. The biggest change was the addition
43 of the "password chat" option, which allows the easy runtime
44 specification of the expected sequence of events to change a
45 password.
48 #include "includes.h"
49 #include "../libcli/auth/libcli_auth.h"
50 #include "../lib/crypto/arcfour.h"
51 #include "rpc_server/srv_samr_util.h"
53 #if ALLOW_CHANGE_PASSWORD
55 static int findpty(char **slave)
57 int master = -1;
58 char *line = NULL;
59 SMB_STRUCT_DIR *dirp = NULL;
60 const char *dpname;
62 *slave = NULL;
64 #if defined(HAVE_GRANTPT)
65 /* Try to open /dev/ptmx. If that fails, fall through to old method. */
66 if ((master = sys_open("/dev/ptmx", O_RDWR, 0)) >= 0) {
67 grantpt(master);
68 unlockpt(master);
69 line = (char *)ptsname(master);
70 if (line) {
71 *slave = SMB_STRDUP(line);
74 if (*slave == NULL) {
75 DEBUG(0,
76 ("findpty: Unable to create master/slave pty pair.\n"));
77 /* Stop fd leak on error. */
78 close(master);
79 return -1;
80 } else {
81 DEBUG(10,
82 ("findpty: Allocated slave pty %s\n", *slave));
83 return (master);
86 #endif /* HAVE_GRANTPT */
88 line = SMB_STRDUP("/dev/ptyXX");
89 if (!line) {
90 return (-1);
93 dirp = sys_opendir("/dev");
94 if (!dirp) {
95 SAFE_FREE(line);
96 return (-1);
99 while ((dpname = readdirname(dirp)) != NULL) {
100 if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5) {
101 DEBUG(3,
102 ("pty: try to open %s, line was %s\n", dpname,
103 line));
104 line[8] = dpname[3];
105 line[9] = dpname[4];
106 if ((master = sys_open(line, O_RDWR, 0)) >= 0) {
107 DEBUG(3, ("pty: opened %s\n", line));
108 line[5] = 't';
109 *slave = line;
110 sys_closedir(dirp);
111 return (master);
115 sys_closedir(dirp);
116 SAFE_FREE(line);
117 return (-1);
120 static int dochild(int master, const char *slavedev, const struct passwd *pass,
121 const char *passwordprogram, bool as_root)
123 int slave;
124 struct termios stermios;
125 gid_t gid;
126 uid_t uid;
127 char * const eptrs[1] = { NULL };
129 if (pass == NULL)
131 DEBUG(0,
132 ("dochild: user doesn't exist in the UNIX password database.\n"));
133 return False;
136 gid = pass->pw_gid;
137 uid = pass->pw_uid;
139 gain_root_privilege();
141 /* Start new session - gets rid of controlling terminal. */
142 if (setsid() < 0)
144 DEBUG(3,
145 ("Weirdness, couldn't let go of controlling terminal\n"));
146 return (False);
149 /* Open slave pty and acquire as new controlling terminal. */
150 if ((slave = sys_open(slavedev, O_RDWR, 0)) < 0)
152 DEBUG(3, ("More weirdness, could not open %s\n", slavedev));
153 return (False);
155 #if defined(TIOCSCTTY) && !defined(SUNOS5)
157 * On patched Solaris 10 TIOCSCTTY is defined but seems not to work,
158 * see the discussion under
159 * https://bugzilla.samba.org/show_bug.cgi?id=5366.
161 if (ioctl(slave, TIOCSCTTY, 0) < 0)
163 DEBUG(3, ("Error in ioctl call for slave pty\n"));
164 /* return(False); */
166 #elif defined(I_PUSH) && defined(I_FIND)
167 if (ioctl(slave, I_FIND, "ptem") == 0) {
168 ioctl(slave, I_PUSH, "ptem");
170 if (ioctl(slave, I_FIND, "ldterm") == 0) {
171 ioctl(slave, I_PUSH, "ldterm");
173 #endif
175 /* Close master. */
176 close(master);
178 /* Make slave stdin/out/err of child. */
180 if (dup2(slave, STDIN_FILENO) != STDIN_FILENO)
182 DEBUG(3, ("Could not re-direct stdin\n"));
183 return (False);
185 if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO)
187 DEBUG(3, ("Could not re-direct stdout\n"));
188 return (False);
190 if (dup2(slave, STDERR_FILENO) != STDERR_FILENO)
192 DEBUG(3, ("Could not re-direct stderr\n"));
193 return (False);
195 if (slave > 2)
196 close(slave);
198 /* Set proper terminal attributes - no echo, canonical input processing,
199 no map NL to CR/NL on output. */
201 if (tcgetattr(0, &stermios) < 0)
203 DEBUG(3,
204 ("could not read default terminal attributes on pty\n"));
205 return (False);
207 stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
208 stermios.c_lflag |= ICANON;
209 #ifdef ONLCR
210 stermios.c_oflag &= ~(ONLCR);
211 #endif
212 if (tcsetattr(0, TCSANOW, &stermios) < 0)
214 DEBUG(3, ("could not set attributes of pty\n"));
215 return (False);
218 /* make us completely into the right uid */
219 if (!as_root)
221 become_user_permanently(uid, gid);
224 DEBUG(10,
225 ("Invoking '%s' as password change program.\n",
226 passwordprogram));
228 /* execl() password-change application */
229 if (execle("/bin/sh", "sh", "-c", passwordprogram, NULL, eptrs) < 0)
231 DEBUG(3, ("Bad status returned from %s\n", passwordprogram));
232 return (False);
234 return (True);
237 static int expect(int master, char *issue, char *expected)
239 char buffer[1024];
240 int attempts, timeout, nread;
241 size_t len;
242 bool match = False;
244 for (attempts = 0; attempts < 2; attempts++) {
245 NTSTATUS status;
246 if (!strequal(issue, ".")) {
247 if (lp_passwd_chat_debug())
248 DEBUG(100, ("expect: sending [%s]\n", issue));
250 if ((len = sys_write(master, issue, strlen(issue))) != strlen(issue)) {
251 DEBUG(2,("expect: (short) write returned %d\n",
252 (int)len ));
253 return False;
257 if (strequal(expected, "."))
258 return True;
260 /* Initial timeout. */
261 timeout = lp_passwd_chat_timeout() * 1000;
262 nread = 0;
263 buffer[nread] = 0;
265 while (True) {
266 status = read_fd_with_timeout(
267 master, buffer + nread, 1,
268 sizeof(buffer) - nread - 1,
269 timeout, &len);
271 if (!NT_STATUS_IS_OK(status)) {
272 break;
274 nread += len;
275 buffer[nread] = 0;
278 /* Eat leading/trailing whitespace before match. */
279 char *str = SMB_STRDUP(buffer);
280 if (!str) {
281 DEBUG(2,("expect: ENOMEM\n"));
282 return False;
284 trim_char(str, ' ', ' ');
286 if ((match = unix_wild_match(expected, str)) == True) {
287 /* Now data has started to return, lower timeout. */
288 timeout = lp_passwd_chat_timeout() * 100;
290 SAFE_FREE(str);
294 if (lp_passwd_chat_debug())
295 DEBUG(100, ("expect: expected [%s] received [%s] match %s\n",
296 expected, buffer, match ? "yes" : "no" ));
298 if (match)
299 break;
301 if (!NT_STATUS_IS_OK(status)) {
302 DEBUG(2, ("expect: %s\n", nt_errstr(status)));
303 return False;
307 DEBUG(10,("expect: returning %s\n", match ? "True" : "False" ));
308 return match;
311 static void pwd_sub(char *buf)
313 all_string_sub(buf, "\\n", "\n", 0);
314 all_string_sub(buf, "\\r", "\r", 0);
315 all_string_sub(buf, "\\s", " ", 0);
316 all_string_sub(buf, "\\t", "\t", 0);
319 static int talktochild(int master, const char *seq)
321 TALLOC_CTX *frame = talloc_stackframe();
322 int count = 0;
323 char *issue;
324 char *expected;
326 issue = talloc_strdup(frame, ".");
327 if (!issue) {
328 TALLOC_FREE(frame);
329 return false;
332 while (next_token_talloc(frame, &seq, &expected, NULL)) {
333 pwd_sub(expected);
334 count++;
336 if (!expect(master, issue, expected)) {
337 DEBUG(3, ("Response %d incorrect\n", count));
338 TALLOC_FREE(frame);
339 return false;
342 if (!next_token_talloc(frame, &seq, &issue, NULL)) {
343 issue = talloc_strdup(frame, ".");
344 if (!issue) {
345 TALLOC_FREE(frame);
346 return false;
349 pwd_sub(issue);
352 if (!strequal(issue, ".")) {
353 /* we have one final issue to send */
354 expected = talloc_strdup(frame, ".");
355 if (!expected) {
356 TALLOC_FREE(frame);
357 return false;
359 if (!expect(master, issue, expected)) {
360 TALLOC_FREE(frame);
361 return False;
364 TALLOC_FREE(frame);
365 return (count > 0);
368 static bool chat_with_program(char *passwordprogram, const struct passwd *pass,
369 char *chatsequence, bool as_root)
371 char *slavedev = NULL;
372 int master;
373 pid_t pid, wpid;
374 int wstat;
375 bool chstat = False;
377 if (pass == NULL) {
378 DEBUG(0, ("chat_with_program: user doesn't exist in the UNIX password database.\n"));
379 return False;
382 /* allocate a pseudo-terminal device */
383 if ((master = findpty(&slavedev)) < 0) {
384 DEBUG(3, ("chat_with_program: Cannot Allocate pty for password change: %s\n", pass->pw_name));
385 return (False);
389 * We need to temporarily stop CatchChild from eating
390 * SIGCLD signals as it also eats the exit status code. JRA.
393 CatchChildLeaveStatus();
395 if ((pid = sys_fork()) < 0) {
396 DEBUG(3, ("chat_with_program: Cannot fork() child for password change: %s\n", pass->pw_name));
397 SAFE_FREE(slavedev);
398 close(master);
399 CatchChild();
400 return (False);
403 /* we now have a pty */
404 if (pid > 0) { /* This is the parent process */
405 /* Don't need this anymore in parent. */
406 SAFE_FREE(slavedev);
408 if ((chstat = talktochild(master, chatsequence)) == False) {
409 DEBUG(3, ("chat_with_program: Child failed to change password: %s\n", pass->pw_name));
410 kill(pid, SIGKILL); /* be sure to end this process */
413 while ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) {
414 if (errno == EINTR) {
415 errno = 0;
416 continue;
418 break;
421 if (wpid < 0) {
422 DEBUG(3, ("chat_with_program: The process is no longer waiting!\n\n"));
423 close(master);
424 CatchChild();
425 return (False);
429 * Go back to ignoring children.
431 CatchChild();
433 close(master);
435 if (pid != wpid) {
436 DEBUG(3, ("chat_with_program: We were waiting for the wrong process ID\n"));
437 return (False);
439 if (WIFEXITED(wstat) && (WEXITSTATUS(wstat) != 0)) {
440 DEBUG(3, ("chat_with_program: The process exited with status %d \
441 while we were waiting\n", WEXITSTATUS(wstat)));
442 return (False);
444 #if defined(WIFSIGNALLED) && defined(WTERMSIG)
445 else if (WIFSIGNALLED(wstat)) {
446 DEBUG(3, ("chat_with_program: The process was killed by signal %d \
447 while we were waiting\n", WTERMSIG(wstat)));
448 return (False);
450 #endif
451 } else {
452 /* CHILD */
455 * Lose any elevated privileges.
457 drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
458 drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
460 /* make sure it doesn't freeze */
461 alarm(20);
463 if (as_root)
464 become_root();
466 DEBUG(3, ("chat_with_program: Dochild for user %s (uid=%d,gid=%d) (as_root = %s)\n", pass->pw_name,
467 (int)getuid(), (int)getgid(), BOOLSTR(as_root) ));
468 chstat = dochild(master, slavedev, pass, passwordprogram, as_root);
470 if (as_root)
471 unbecome_root();
474 * The child should never return from dochild() ....
477 DEBUG(0, ("chat_with_program: Error: dochild() returned %d\n", chstat));
478 exit(1);
481 if (chstat)
482 DEBUG(3, ("chat_with_program: Password change %ssuccessful for user %s\n",
483 (chstat ? "" : "un"), pass->pw_name));
484 return (chstat);
487 bool chgpasswd(const char *name, const struct passwd *pass,
488 const char *oldpass, const char *newpass, bool as_root)
490 char *passwordprogram = NULL;
491 char *chatsequence = NULL;
492 size_t i;
493 size_t len;
494 TALLOC_CTX *ctx = talloc_tos();
496 if (!oldpass) {
497 oldpass = "";
500 DEBUG(3, ("chgpasswd: Password change (as_root=%s) for user: %s\n", BOOLSTR(as_root), name));
502 #ifdef DEBUG_PASSWORD
503 DEBUG(100, ("chgpasswd: Passwords: old=%s new=%s\n", oldpass, newpass));
504 #endif
506 /* Take the passed information and test it for minimum criteria */
508 /* Password is same as old password */
509 if (strcmp(oldpass, newpass) == 0) {
510 /* don't allow same password */
511 DEBUG(2, ("chgpasswd: Password Change: %s, New password is same as old\n", name)); /* log the attempt */
512 return (False); /* inform the user */
516 * Check the old and new passwords don't contain any control
517 * characters.
520 len = strlen(oldpass);
521 for (i = 0; i < len; i++) {
522 if (iscntrl((int)oldpass[i])) {
523 DEBUG(0, ("chgpasswd: oldpass contains control characters (disallowed).\n"));
524 return False;
528 len = strlen(newpass);
529 for (i = 0; i < len; i++) {
530 if (iscntrl((int)newpass[i])) {
531 DEBUG(0, ("chgpasswd: newpass contains control characters (disallowed).\n"));
532 return False;
536 #ifdef WITH_PAM
537 if (lp_pam_password_change()) {
538 bool ret;
539 #ifdef HAVE_SETLOCALE
540 const char *prevlocale = setlocale(LC_ALL, "C");
541 #endif
543 if (as_root)
544 become_root();
546 if (pass) {
547 ret = smb_pam_passchange(pass->pw_name, oldpass, newpass);
548 } else {
549 ret = smb_pam_passchange(name, oldpass, newpass);
552 if (as_root)
553 unbecome_root();
555 #ifdef HAVE_SETLOCALE
556 setlocale(LC_ALL, prevlocale);
557 #endif
559 return ret;
561 #endif
563 /* A non-PAM password change just doen't make sense without a valid local user */
565 if (pass == NULL) {
566 DEBUG(0, ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", name));
567 return false;
570 passwordprogram = talloc_strdup(ctx, lp_passwd_program());
571 if (!passwordprogram || !*passwordprogram) {
572 DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
573 return false;
575 chatsequence = talloc_strdup(ctx, lp_passwd_chat());
576 if (!chatsequence || !*chatsequence) {
577 DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
578 return false;
581 if (as_root) {
582 /* The password program *must* contain the user name to work. Fail if not. */
583 if (strstr_m(passwordprogram, "%u") == NULL) {
584 DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
585 the string %%u, and the given string %s does not.\n", passwordprogram ));
586 return false;
590 passwordprogram = talloc_string_sub(ctx, passwordprogram, "%u", name);
591 if (!passwordprogram) {
592 return false;
595 /* note that we do NOT substitute the %o and %n in the password program
596 as this would open up a security hole where the user could use
597 a new password containing shell escape characters */
599 chatsequence = talloc_string_sub(ctx, chatsequence, "%u", name);
600 if (!chatsequence) {
601 return false;
603 chatsequence = talloc_all_string_sub(ctx,
604 chatsequence,
605 "%o",
606 oldpass);
607 if (!chatsequence) {
608 return false;
610 chatsequence = talloc_all_string_sub(ctx,
611 chatsequence,
612 "%n",
613 newpass);
614 return chat_with_program(passwordprogram,
615 pass,
616 chatsequence,
617 as_root);
620 #else /* ALLOW_CHANGE_PASSWORD */
622 bool chgpasswd(const char *name, const struct passwd *pass,
623 const char *oldpass, const char *newpass, bool as_root)
625 DEBUG(0, ("chgpasswd: Unix Password changing not compiled in (user=%s)\n", name));
626 return (False);
628 #endif /* ALLOW_CHANGE_PASSWORD */
630 /***********************************************************
631 Decrypt and verify a user password change.
633 The 516 byte long buffers are encrypted with the old NT and
634 old LM passwords, and if the NT passwords are present, both
635 buffers contain a unicode string.
637 After decrypting the buffers, check the password is correct by
638 matching the old hashed passwords with the passwords in the passdb.
640 ************************************************************/
642 static NTSTATUS check_oem_password(const char *user,
643 uchar password_encrypted_with_lm_hash[516],
644 const uchar old_lm_hash_encrypted[16],
645 uchar password_encrypted_with_nt_hash[516],
646 const uchar old_nt_hash_encrypted[16],
647 struct samu *sampass,
648 char **pp_new_passwd)
650 uchar null_pw[16];
651 uchar null_ntpw[16];
652 uint8 *password_encrypted;
653 const uint8 *encryption_key;
654 const uint8 *lanman_pw, *nt_pw;
655 uint32 acct_ctrl;
656 size_t new_pw_len;
657 uchar new_nt_hash[16];
658 uchar new_lm_hash[16];
659 uchar verifier[16];
660 char no_pw[2];
662 bool nt_pass_set = (password_encrypted_with_nt_hash && old_nt_hash_encrypted);
663 bool lm_pass_set = (password_encrypted_with_lm_hash && old_lm_hash_encrypted);
665 acct_ctrl = pdb_get_acct_ctrl(sampass);
666 #if 0
667 /* I am convinced this check here is wrong, it is valid to
668 * change a password of a user that has a disabled account - gd */
670 if (acct_ctrl & ACB_DISABLED) {
671 DEBUG(2,("check_lanman_password: account %s disabled.\n", user));
672 return NT_STATUS_ACCOUNT_DISABLED;
674 #endif
675 if ((acct_ctrl & ACB_PWNOTREQ) && lp_null_passwords()) {
676 /* construct a null password (in case one is needed */
677 no_pw[0] = 0;
678 no_pw[1] = 0;
679 nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
680 lanman_pw = null_pw;
681 nt_pw = null_pw;
683 } else {
684 /* save pointers to passwords so we don't have to keep looking them up */
685 if (lp_lanman_auth()) {
686 lanman_pw = pdb_get_lanman_passwd(sampass);
687 } else {
688 lanman_pw = NULL;
690 nt_pw = pdb_get_nt_passwd(sampass);
693 if (nt_pw && nt_pass_set) {
694 /* IDEAL Case: passwords are in unicode, and we can
695 * read use the password encrypted with the NT hash
697 password_encrypted = password_encrypted_with_nt_hash;
698 encryption_key = nt_pw;
699 } else if (lanman_pw && lm_pass_set) {
700 /* password may still be in unicode, but use LM hash version */
701 password_encrypted = password_encrypted_with_lm_hash;
702 encryption_key = lanman_pw;
703 } else if (nt_pass_set) {
704 DEBUG(1, ("NT password change supplied for user %s, but we have no NT password to check it with\n",
705 user));
706 return NT_STATUS_WRONG_PASSWORD;
707 } else if (lm_pass_set) {
708 if (lp_lanman_auth()) {
709 DEBUG(1, ("LM password change supplied for user %s, but we have no LanMan password to check it with\n",
710 user));
711 } else {
712 DEBUG(1, ("LM password change supplied for user %s, but we have disabled LanMan authentication\n",
713 user));
715 return NT_STATUS_WRONG_PASSWORD;
716 } else {
717 DEBUG(1, ("password change requested for user %s, but no password supplied!\n",
718 user));
719 return NT_STATUS_WRONG_PASSWORD;
723 * Decrypt the password with the key
725 arcfour_crypt( password_encrypted, encryption_key, 516);
727 if (!decode_pw_buffer(talloc_tos(),
728 password_encrypted,
729 pp_new_passwd,
730 &new_pw_len,
731 nt_pass_set ? CH_UTF16 : CH_DOS)) {
732 return NT_STATUS_WRONG_PASSWORD;
736 * To ensure we got the correct new password, hash it and
737 * use it as a key to test the passed old password.
740 if (nt_pass_set) {
741 /* NT passwords, verify the NT hash. */
743 /* Calculate the MD4 hash (NT compatible) of the password */
744 memset(new_nt_hash, '\0', 16);
745 E_md4hash(*pp_new_passwd, new_nt_hash);
747 if (nt_pw) {
749 * check the NT verifier
751 E_old_pw_hash(new_nt_hash, nt_pw, verifier);
752 if (memcmp(verifier, old_nt_hash_encrypted, 16)) {
753 DEBUG(0, ("check_oem_password: old nt "
754 "password doesn't match.\n"));
755 return NT_STATUS_WRONG_PASSWORD;
758 /* We could check the LM password here, but there is
759 * little point, we already know the password is
760 * correct, and the LM password might not even be
761 * present. */
763 /* Further, LM hash generation algorithms
764 * differ with charset, so we could
765 * incorrectly fail a perfectly valid password
766 * change */
767 #ifdef DEBUG_PASSWORD
768 DEBUG(100,
769 ("check_oem_password: password %s ok\n", *pp_new_passwd));
770 #endif
771 return NT_STATUS_OK;
774 if (lanman_pw) {
776 * check the lm verifier
778 E_old_pw_hash(new_nt_hash, lanman_pw, verifier);
779 if (memcmp(verifier, old_lm_hash_encrypted, 16)) {
780 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
781 return NT_STATUS_WRONG_PASSWORD;
783 #ifdef DEBUG_PASSWORD
784 DEBUG(100,
785 ("check_oem_password: password %s ok\n", *pp_new_passwd));
786 #endif
787 return NT_STATUS_OK;
791 if (lanman_pw && lm_pass_set) {
793 E_deshash(*pp_new_passwd, new_lm_hash);
796 * check the lm verifier
798 E_old_pw_hash(new_lm_hash, lanman_pw, verifier);
799 if (memcmp(verifier, old_lm_hash_encrypted, 16)) {
800 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
801 return NT_STATUS_WRONG_PASSWORD;
804 #ifdef DEBUG_PASSWORD
805 DEBUG(100,
806 ("check_oem_password: password %s ok\n", *pp_new_passwd));
807 #endif
808 return NT_STATUS_OK;
811 /* should not be reached */
812 return NT_STATUS_WRONG_PASSWORD;
815 static bool password_in_history(uint8_t nt_pw[NT_HASH_LEN],
816 uint32_t pw_history_len,
817 const uint8_t *pw_history)
819 static const uint8_t zero_md5_nt_pw[SALTED_MD5_HASH_LEN] = { 0, };
820 int i;
822 dump_data(100, nt_pw, NT_HASH_LEN);
823 dump_data(100, pw_history, PW_HISTORY_ENTRY_LEN * pw_history_len);
825 for (i=0; i<pw_history_len; i++) {
826 uint8_t new_nt_pw_salted_md5_hash[SALTED_MD5_HASH_LEN];
827 const uint8_t *current_salt;
828 const uint8_t *old_nt_pw_salted_md5_hash;
830 current_salt = &pw_history[i*PW_HISTORY_ENTRY_LEN];
831 old_nt_pw_salted_md5_hash = current_salt + PW_HISTORY_SALT_LEN;
833 if (memcmp(zero_md5_nt_pw, old_nt_pw_salted_md5_hash,
834 SALTED_MD5_HASH_LEN) == 0) {
835 /* Ignore zero valued entries. */
836 continue;
839 if (memcmp(zero_md5_nt_pw, current_salt,
840 PW_HISTORY_SALT_LEN) == 0)
843 * New format: zero salt and then plain nt hash.
844 * Directly compare the hashes.
846 if (memcmp(nt_pw, old_nt_pw_salted_md5_hash,
847 SALTED_MD5_HASH_LEN) == 0)
849 return true;
851 } else {
853 * Old format: md5sum of salted nt hash.
854 * Create salted version of new pw to compare.
856 E_md5hash(current_salt, nt_pw, new_nt_pw_salted_md5_hash);
858 if (memcmp(new_nt_pw_salted_md5_hash,
859 old_nt_pw_salted_md5_hash,
860 SALTED_MD5_HASH_LEN) == 0) {
861 return true;
865 return false;
868 /***********************************************************
869 This routine takes the given password and checks it against
870 the password history. Returns True if this password has been
871 found in the history list.
872 ************************************************************/
874 static bool check_passwd_history(struct samu *sampass, const char *plaintext)
876 uchar new_nt_p16[NT_HASH_LEN];
877 const uint8 *nt_pw;
878 const uint8 *pwhistory;
879 uint32 pwHisLen, curr_pwHisLen;
881 pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHisLen);
882 if (pwHisLen == 0) {
883 return False;
886 pwhistory = pdb_get_pw_history(sampass, &curr_pwHisLen);
887 if (!pwhistory || curr_pwHisLen == 0) {
888 return False;
891 /* Only examine the minimum of the current history len and
892 the stored history len. Avoids race conditions. */
893 pwHisLen = MIN(pwHisLen,curr_pwHisLen);
895 nt_pw = pdb_get_nt_passwd(sampass);
897 E_md4hash(plaintext, new_nt_p16);
899 if (!memcmp(nt_pw, new_nt_p16, NT_HASH_LEN)) {
900 DEBUG(10,("check_passwd_history: proposed new password for user %s is the same as the current password !\n",
901 pdb_get_username(sampass) ));
902 return True;
905 if (password_in_history(new_nt_p16, pwHisLen, pwhistory)) {
906 DEBUG(1,("check_passwd_history: proposed new password for "
907 "user %s found in history list !\n",
908 pdb_get_username(sampass) ));
909 return true;
911 return false;
914 /***********************************************************
915 ************************************************************/
917 NTSTATUS check_password_complexity(const char *username,
918 const char *password,
919 enum samPwdChangeReason *samr_reject_reason)
921 TALLOC_CTX *tosctx = talloc_tos();
922 int check_ret;
923 char *cmd;
925 /* Use external script to check password complexity */
926 if ((lp_check_password_script() == NULL)
927 || (*(lp_check_password_script()) == '\0')) {
928 return NT_STATUS_OK;
931 cmd = talloc_string_sub(tosctx, lp_check_password_script(), "%u",
932 username);
933 if (!cmd) {
934 return NT_STATUS_PASSWORD_RESTRICTION;
937 check_ret = smbrunsecret(cmd, password);
938 DEBUG(5,("check_password_complexity: check password script (%s) "
939 "returned [%d]\n", cmd, check_ret));
940 TALLOC_FREE(cmd);
942 if (check_ret != 0) {
943 DEBUG(1,("check_password_complexity: "
944 "check password script said new password is not good "
945 "enough!\n"));
946 if (samr_reject_reason) {
947 *samr_reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX;
949 return NT_STATUS_PASSWORD_RESTRICTION;
952 return NT_STATUS_OK;
955 /***********************************************************
956 Code to change the oem password. Changes both the lanman
957 and NT hashes. Old_passwd is almost always NULL.
958 NOTE this function is designed to be called as root. Check the old password
959 is correct before calling. JRA.
960 ************************************************************/
962 static NTSTATUS change_oem_password(struct samu *hnd, char *old_passwd, char *new_passwd, bool as_root, enum samPwdChangeReason *samr_reject_reason)
964 uint32 min_len;
965 uint32 refuse;
966 TALLOC_CTX *tosctx = talloc_tos();
967 struct passwd *pass = NULL;
968 const char *username = pdb_get_username(hnd);
969 time_t can_change_time = pdb_get_pass_can_change_time(hnd);
970 NTSTATUS status;
972 if (samr_reject_reason) {
973 *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
976 /* check to see if the secdesc has previously been set to disallow */
977 if (!pdb_get_pass_can_change(hnd)) {
978 DEBUG(1, ("user %s does not have permissions to change password\n", username));
979 if (samr_reject_reason) {
980 *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
982 return NT_STATUS_ACCOUNT_RESTRICTION;
985 /* check to see if it is a Machine account and if the policy
986 * denies machines to change the password. *
987 * Should we deny also SRVTRUST and/or DOMSTRUST ? .SSS. */
988 if (pdb_get_acct_ctrl(hnd) & ACB_WSTRUST) {
989 if (pdb_get_account_policy(PDB_POLICY_REFUSE_MACHINE_PW_CHANGE, &refuse) && refuse) {
990 DEBUG(1, ("Machine %s cannot change password now, "
991 "denied by Refuse Machine Password Change policy\n",
992 username));
993 if (samr_reject_reason) {
994 *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
996 return NT_STATUS_ACCOUNT_RESTRICTION;
1000 /* removed calculation here, because passdb now calculates
1001 based on policy. jmcd */
1002 if ((can_change_time != 0) && (time(NULL) < can_change_time)) {
1003 DEBUG(1, ("user %s cannot change password now, must "
1004 "wait until %s\n", username,
1005 http_timestring(tosctx, can_change_time)));
1006 if (samr_reject_reason) {
1007 *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
1009 return NT_STATUS_ACCOUNT_RESTRICTION;
1012 if (pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, &min_len) && (str_charnum(new_passwd) < min_len)) {
1013 DEBUG(1, ("user %s cannot change password - password too short\n",
1014 username));
1015 DEBUGADD(1, (" account policy min password len = %d\n", min_len));
1016 if (samr_reject_reason) {
1017 *samr_reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT;
1019 return NT_STATUS_PASSWORD_RESTRICTION;
1020 /* return NT_STATUS_PWD_TOO_SHORT; */
1023 if (check_passwd_history(hnd,new_passwd)) {
1024 if (samr_reject_reason) {
1025 *samr_reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
1027 return NT_STATUS_PASSWORD_RESTRICTION;
1030 pass = Get_Pwnam_alloc(tosctx, username);
1031 if (!pass) {
1032 DEBUG(1, ("change_oem_password: Username %s does not exist in system !?!\n", username));
1033 return NT_STATUS_ACCESS_DENIED;
1036 status = check_password_complexity(username, new_passwd, samr_reject_reason);
1037 if (!NT_STATUS_IS_OK(status)) {
1038 TALLOC_FREE(pass);
1039 return status;
1043 * If unix password sync was requested, attempt to change
1044 * the /etc/passwd database first. Return failure if this cannot
1045 * be done.
1047 * This occurs before the oem change, because we don't want to
1048 * update it if chgpasswd failed.
1050 * Conditional on lp_unix_password_sync() because we don't want
1051 * to touch the unix db unless we have admin permission.
1054 if(lp_unix_password_sync() &&
1055 !chgpasswd(username, pass, old_passwd, new_passwd, as_root)) {
1056 TALLOC_FREE(pass);
1057 return NT_STATUS_ACCESS_DENIED;
1060 TALLOC_FREE(pass);
1062 if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
1063 return NT_STATUS_ACCESS_DENIED;
1066 /* Now write it into the file. */
1067 return pdb_update_sam_account (hnd);
1070 /***********************************************************
1071 Code to check and change the OEM hashed password.
1072 ************************************************************/
1074 NTSTATUS pass_oem_change(char *user,
1075 uchar password_encrypted_with_lm_hash[516],
1076 const uchar old_lm_hash_encrypted[16],
1077 uchar password_encrypted_with_nt_hash[516],
1078 const uchar old_nt_hash_encrypted[16],
1079 enum samPwdChangeReason *reject_reason)
1081 char *new_passwd = NULL;
1082 struct samu *sampass = NULL;
1083 NTSTATUS nt_status;
1084 bool ret = false;
1086 if (!(sampass = samu_new(NULL))) {
1087 return NT_STATUS_NO_MEMORY;
1090 become_root();
1091 ret = pdb_getsampwnam(sampass, user);
1092 unbecome_root();
1094 if (ret == false) {
1095 DEBUG(0,("pass_oem_change: getsmbpwnam returned NULL\n"));
1096 TALLOC_FREE(sampass);
1097 return NT_STATUS_NO_SUCH_USER;
1100 nt_status = check_oem_password(user,
1101 password_encrypted_with_lm_hash,
1102 old_lm_hash_encrypted,
1103 password_encrypted_with_nt_hash,
1104 old_nt_hash_encrypted,
1105 sampass,
1106 &new_passwd);
1108 if (!NT_STATUS_IS_OK(nt_status)) {
1109 TALLOC_FREE(sampass);
1110 return nt_status;
1113 /* We've already checked the old password here.... */
1114 become_root();
1115 nt_status = change_oem_password(sampass, NULL, new_passwd, True, reject_reason);
1116 unbecome_root();
1118 memset(new_passwd, 0, strlen(new_passwd));
1120 TALLOC_FREE(sampass);
1122 return nt_status;