dbwrap: Use tdb_null in db_ctdb_delete
[Samba/gbeck.git] / source3 / rpc_server / samr / srv_samr_chgpasswd.c
blobdb1f459c8d4e3db638840038144dc5fc062dfad4
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 "system/terminal.h"
50 #include "system/passwd.h"
51 #include "system/filesys.h"
52 #include "../libcli/auth/libcli_auth.h"
53 #include "../lib/crypto/arcfour.h"
54 #include "rpc_server/samr/srv_samr_util.h"
55 #include "passdb.h"
56 #include "auth.h"
58 #ifndef ALLOW_CHANGE_PASSWORD
59 #if (defined(HAVE_TERMIOS_H) && defined(HAVE_DUP2) && defined(HAVE_SETSID))
60 #define ALLOW_CHANGE_PASSWORD 1
61 #endif
62 #endif
64 #if ALLOW_CHANGE_PASSWORD
66 static int findpty(char **slave)
68 int master = -1;
69 char *line = NULL;
70 DIR *dirp = NULL;
71 const char *dpname;
73 *slave = NULL;
75 #if defined(HAVE_GRANTPT)
76 #if defined(HAVE_POSIX_OPENPT)
77 master = posix_openpt(O_RDWR|O_NOCTTY);
78 #else
79 /* Try to open /dev/ptmx. If that fails, fall through to old method. */
80 master = open("/dev/ptmx", O_RDWR, 0);
81 #endif
82 if (master >= 0) {
83 grantpt(master);
84 unlockpt(master);
85 line = (char *)ptsname(master);
86 if (line) {
87 *slave = SMB_STRDUP(line);
90 if (*slave == NULL) {
91 DEBUG(0,
92 ("findpty: Unable to create master/slave pty pair.\n"));
93 /* Stop fd leak on error. */
94 close(master);
95 return -1;
96 } else {
97 DEBUG(10,
98 ("findpty: Allocated slave pty %s\n", *slave));
99 return (master);
102 #endif /* HAVE_GRANTPT */
104 line = SMB_STRDUP("/dev/ptyXX");
105 if (!line) {
106 return (-1);
109 dirp = opendir("/dev");
110 if (!dirp) {
111 SAFE_FREE(line);
112 return (-1);
115 while ((dpname = readdirname(dirp)) != NULL) {
116 if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5) {
117 DEBUG(3,
118 ("pty: try to open %s, line was %s\n", dpname,
119 line));
120 line[8] = dpname[3];
121 line[9] = dpname[4];
122 if ((master = open(line, O_RDWR, 0)) >= 0) {
123 DEBUG(3, ("pty: opened %s\n", line));
124 line[5] = 't';
125 *slave = line;
126 closedir(dirp);
127 return (master);
131 closedir(dirp);
132 SAFE_FREE(line);
133 return (-1);
136 static int dochild(int master, const char *slavedev, const struct passwd *pass,
137 const char *passwordprogram, bool as_root)
139 int slave;
140 struct termios stermios;
141 gid_t gid;
142 uid_t uid;
143 char * const eptrs[1] = { NULL };
145 if (pass == NULL)
147 DEBUG(0,
148 ("dochild: user doesn't exist in the UNIX password database.\n"));
149 return False;
152 gid = pass->pw_gid;
153 uid = pass->pw_uid;
155 gain_root_privilege();
157 /* Start new session - gets rid of controlling terminal. */
158 if (setsid() < 0)
160 DEBUG(3,
161 ("Weirdness, couldn't let go of controlling terminal\n"));
162 return (False);
165 /* Open slave pty and acquire as new controlling terminal. */
166 if ((slave = open(slavedev, O_RDWR, 0)) < 0)
168 DEBUG(3, ("More weirdness, could not open %s\n", slavedev));
169 return (False);
171 #if defined(TIOCSCTTY) && !defined(SUNOS5)
173 * On patched Solaris 10 TIOCSCTTY is defined but seems not to work,
174 * see the discussion under
175 * https://bugzilla.samba.org/show_bug.cgi?id=5366.
177 if (ioctl(slave, TIOCSCTTY, 0) < 0)
179 DEBUG(3, ("Error in ioctl call for slave pty\n"));
180 /* return(False); */
182 #elif defined(I_PUSH) && defined(I_FIND)
183 if (ioctl(slave, I_FIND, "ptem") == 0) {
184 ioctl(slave, I_PUSH, "ptem");
186 if (ioctl(slave, I_FIND, "ldterm") == 0) {
187 ioctl(slave, I_PUSH, "ldterm");
189 #endif
191 /* Close master. */
192 close(master);
194 /* Make slave stdin/out/err of child. */
196 if (dup2(slave, STDIN_FILENO) != STDIN_FILENO)
198 DEBUG(3, ("Could not re-direct stdin\n"));
199 return (False);
201 if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO)
203 DEBUG(3, ("Could not re-direct stdout\n"));
204 return (False);
206 if (dup2(slave, STDERR_FILENO) != STDERR_FILENO)
208 DEBUG(3, ("Could not re-direct stderr\n"));
209 return (False);
211 if (slave > 2)
212 close(slave);
214 /* Set proper terminal attributes - no echo, canonical input processing,
215 no map NL to CR/NL on output. */
217 if (tcgetattr(0, &stermios) < 0)
219 DEBUG(3,
220 ("could not read default terminal attributes on pty\n"));
221 return (False);
223 stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
224 stermios.c_lflag |= ICANON;
225 #ifdef ONLCR
226 stermios.c_oflag &= ~(ONLCR);
227 #endif
228 if (tcsetattr(0, TCSANOW, &stermios) < 0)
230 DEBUG(3, ("could not set attributes of pty\n"));
231 return (False);
234 /* make us completely into the right uid */
235 if (!as_root)
237 become_user_permanently(uid, gid);
240 DEBUG(10,
241 ("Invoking '%s' as password change program.\n",
242 passwordprogram));
244 /* execl() password-change application */
245 if (execle("/bin/sh", "sh", "-c", passwordprogram, NULL, eptrs) < 0)
247 DEBUG(3, ("Bad status returned from %s\n", passwordprogram));
248 return (False);
250 return (True);
253 static int expect(int master, char *issue, char *expected)
255 char buffer[1024];
256 int attempts, timeout, nread;
257 size_t len;
258 bool match = False;
260 for (attempts = 0; attempts < 2; attempts++) {
261 NTSTATUS status;
262 if (!strequal(issue, ".")) {
263 if (lp_passwd_chat_debug())
264 DEBUG(100, ("expect: sending [%s]\n", issue));
266 if ((len = sys_write(master, issue, strlen(issue))) != strlen(issue)) {
267 DEBUG(2,("expect: (short) write returned %d\n",
268 (int)len ));
269 return False;
273 if (strequal(expected, "."))
274 return True;
276 /* Initial timeout. */
277 timeout = lp_passwd_chat_timeout() * 1000;
278 nread = 0;
279 buffer[nread] = 0;
281 while (True) {
282 status = read_fd_with_timeout(
283 master, buffer + nread, 1,
284 sizeof(buffer) - nread - 1,
285 timeout, &len);
287 if (!NT_STATUS_IS_OK(status)) {
288 DEBUG(2, ("expect: read error %s\n",
289 nt_errstr(status)));
290 break;
292 nread += len;
293 buffer[nread] = 0;
296 /* Eat leading/trailing whitespace before match. */
297 char *str = SMB_STRDUP(buffer);
298 if (!str) {
299 DEBUG(2,("expect: ENOMEM\n"));
300 return False;
302 trim_char(str, ' ', ' ');
304 if ((match = unix_wild_match(expected, str)) == True) {
305 /* Now data has started to return, lower timeout. */
306 timeout = lp_passwd_chat_timeout() * 100;
308 SAFE_FREE(str);
312 if (lp_passwd_chat_debug())
313 DEBUG(100, ("expect: expected [%s] received [%s] match %s\n",
314 expected, buffer, match ? "yes" : "no" ));
316 if (match)
317 break;
319 if (!NT_STATUS_IS_OK(status)) {
320 DEBUG(2, ("expect: %s\n", nt_errstr(status)));
321 return False;
325 DEBUG(10,("expect: returning %s\n", match ? "True" : "False" ));
326 return match;
329 static void pwd_sub(char *buf)
331 all_string_sub(buf, "\\n", "\n", 0);
332 all_string_sub(buf, "\\r", "\r", 0);
333 all_string_sub(buf, "\\s", " ", 0);
334 all_string_sub(buf, "\\t", "\t", 0);
337 static int talktochild(int master, const char *seq)
339 TALLOC_CTX *frame = talloc_stackframe();
340 int count = 0;
341 char *issue;
342 char *expected;
344 issue = talloc_strdup(frame, ".");
345 if (!issue) {
346 TALLOC_FREE(frame);
347 return false;
350 while (next_token_talloc(frame, &seq, &expected, NULL)) {
351 pwd_sub(expected);
352 count++;
354 if (!expect(master, issue, expected)) {
355 DEBUG(3, ("Response %d incorrect\n", count));
356 TALLOC_FREE(frame);
357 return false;
360 if (!next_token_talloc(frame, &seq, &issue, NULL)) {
361 issue = talloc_strdup(frame, ".");
362 if (!issue) {
363 TALLOC_FREE(frame);
364 return false;
367 pwd_sub(issue);
370 if (!strequal(issue, ".")) {
371 /* we have one final issue to send */
372 expected = talloc_strdup(frame, ".");
373 if (!expected) {
374 TALLOC_FREE(frame);
375 return false;
377 if (!expect(master, issue, expected)) {
378 TALLOC_FREE(frame);
379 return False;
382 TALLOC_FREE(frame);
383 return (count > 0);
386 static bool chat_with_program(char *passwordprogram, const struct passwd *pass,
387 char *chatsequence, bool as_root)
389 char *slavedev = NULL;
390 int master;
391 pid_t pid, wpid;
392 int wstat;
393 bool chstat = False;
395 if (pass == NULL) {
396 DEBUG(0, ("chat_with_program: user doesn't exist in the UNIX password database.\n"));
397 return False;
400 /* allocate a pseudo-terminal device */
401 if ((master = findpty(&slavedev)) < 0) {
402 DEBUG(3, ("chat_with_program: Cannot Allocate pty for password change: %s\n", pass->pw_name));
403 return (False);
407 * We need to temporarily stop CatchChild from eating
408 * SIGCLD signals as it also eats the exit status code. JRA.
411 CatchChildLeaveStatus();
413 if ((pid = fork()) < 0) {
414 DEBUG(3, ("chat_with_program: Cannot fork() child for password change: %s\n", pass->pw_name));
415 SAFE_FREE(slavedev);
416 close(master);
417 CatchChild();
418 return (False);
421 /* we now have a pty */
422 if (pid > 0) { /* This is the parent process */
423 /* Don't need this anymore in parent. */
424 SAFE_FREE(slavedev);
426 if ((chstat = talktochild(master, chatsequence)) == False) {
427 DEBUG(3, ("chat_with_program: Child failed to change password: %s\n", pass->pw_name));
428 kill(pid, SIGKILL); /* be sure to end this process */
431 while ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) {
432 if (errno == EINTR) {
433 errno = 0;
434 continue;
436 break;
439 if (wpid < 0) {
440 DEBUG(3, ("chat_with_program: The process is no longer waiting!\n\n"));
441 close(master);
442 CatchChild();
443 return (False);
447 * Go back to ignoring children.
449 CatchChild();
451 close(master);
453 if (pid != wpid) {
454 DEBUG(3, ("chat_with_program: We were waiting for the wrong process ID\n"));
455 return (False);
457 if (WIFEXITED(wstat) && (WEXITSTATUS(wstat) != 0)) {
458 DEBUG(3, ("chat_with_program: The process exited with status %d \
459 while we were waiting\n", WEXITSTATUS(wstat)));
460 return (False);
462 #if defined(WIFSIGNALLED) && defined(WTERMSIG)
463 else if (WIFSIGNALLED(wstat)) {
464 DEBUG(3, ("chat_with_program: The process was killed by signal %d \
465 while we were waiting\n", WTERMSIG(wstat)));
466 return (False);
468 #endif
469 } else {
470 /* CHILD */
473 * Lose any elevated privileges.
475 drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
476 drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
478 /* make sure it doesn't freeze */
479 alarm(20);
481 if (as_root)
482 become_root();
484 DEBUG(3, ("chat_with_program: Dochild for user %s (uid=%d,gid=%d) (as_root = %s)\n", pass->pw_name,
485 (int)getuid(), (int)getgid(), BOOLSTR(as_root) ));
486 chstat = dochild(master, slavedev, pass, passwordprogram, as_root);
488 if (as_root)
489 unbecome_root();
492 * The child should never return from dochild() ....
495 DEBUG(0, ("chat_with_program: Error: dochild() returned %d\n", chstat));
496 exit(1);
499 if (chstat)
500 DEBUG(3, ("chat_with_program: Password change %ssuccessful for user %s\n",
501 (chstat ? "" : "un"), pass->pw_name));
502 return (chstat);
505 bool chgpasswd(const char *name, const char *rhost, const struct passwd *pass,
506 const char *oldpass, const char *newpass, bool as_root)
508 char *passwordprogram = NULL;
509 char *chatsequence = NULL;
510 size_t i;
511 size_t len;
512 TALLOC_CTX *ctx = talloc_tos();
514 if (!oldpass) {
515 oldpass = "";
518 DEBUG(3, ("chgpasswd: Password change (as_root=%s) for user: %s\n", BOOLSTR(as_root), name));
520 #ifdef DEBUG_PASSWORD
521 DEBUG(100, ("chgpasswd: Passwords: old=%s new=%s\n", oldpass, newpass));
522 #endif
524 /* Take the passed information and test it for minimum criteria */
526 /* Password is same as old password */
527 if (strcmp(oldpass, newpass) == 0) {
528 /* don't allow same password */
529 DEBUG(2, ("chgpasswd: Password Change: %s, New password is same as old\n", name)); /* log the attempt */
530 return (False); /* inform the user */
534 * Check the old and new passwords don't contain any control
535 * characters.
538 len = strlen(oldpass);
539 for (i = 0; i < len; i++) {
540 if (iscntrl((int)oldpass[i])) {
541 DEBUG(0, ("chgpasswd: oldpass contains control characters (disallowed).\n"));
542 return False;
546 len = strlen(newpass);
547 for (i = 0; i < len; i++) {
548 if (iscntrl((int)newpass[i])) {
549 DEBUG(0, ("chgpasswd: newpass contains control characters (disallowed).\n"));
550 return False;
554 #ifdef WITH_PAM
555 if (lp_pam_password_change()) {
556 bool ret;
557 #ifdef HAVE_SETLOCALE
558 const char *prevlocale = setlocale(LC_ALL, "C");
559 #endif
561 if (as_root)
562 become_root();
564 if (pass) {
565 ret = smb_pam_passchange(pass->pw_name, rhost,
566 oldpass, newpass);
567 } else {
568 ret = smb_pam_passchange(name, rhost, oldpass,
569 newpass);
572 if (as_root)
573 unbecome_root();
575 #ifdef HAVE_SETLOCALE
576 setlocale(LC_ALL, prevlocale);
577 #endif
579 return ret;
581 #endif
583 /* A non-PAM password change just doen't make sense without a valid local user */
585 if (pass == NULL) {
586 DEBUG(0, ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", name));
587 return false;
590 passwordprogram = lp_passwd_program(ctx);
591 if (!passwordprogram || !*passwordprogram) {
592 DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
593 return false;
595 chatsequence = lp_passwd_chat(ctx);
596 if (!chatsequence || !*chatsequence) {
597 DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
598 return false;
601 if (as_root) {
602 /* The password program *must* contain the user name to work. Fail if not. */
603 if (strstr_m(passwordprogram, "%u") == NULL) {
604 DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
605 the string %%u, and the given string %s does not.\n", passwordprogram ));
606 return false;
610 passwordprogram = talloc_string_sub(ctx, passwordprogram, "%u", name);
611 if (!passwordprogram) {
612 return false;
615 /* note that we do NOT substitute the %o and %n in the password program
616 as this would open up a security hole where the user could use
617 a new password containing shell escape characters */
619 chatsequence = talloc_string_sub(ctx, chatsequence, "%u", name);
620 if (!chatsequence) {
621 return false;
623 chatsequence = talloc_all_string_sub(ctx,
624 chatsequence,
625 "%o",
626 oldpass);
627 if (!chatsequence) {
628 return false;
630 chatsequence = talloc_all_string_sub(ctx,
631 chatsequence,
632 "%n",
633 newpass);
634 return chat_with_program(passwordprogram,
635 pass,
636 chatsequence,
637 as_root);
640 #else /* ALLOW_CHANGE_PASSWORD */
642 bool chgpasswd(const char *name, const struct passwd *pass,
643 const char *oldpass, const char *newpass, bool as_root)
645 DEBUG(0, ("chgpasswd: Unix Password changing not compiled in (user=%s)\n", name));
646 return (False);
648 #endif /* ALLOW_CHANGE_PASSWORD */
650 /***********************************************************
651 Decrypt and verify a user password change.
653 The 516 byte long buffers are encrypted with the old NT and
654 old LM passwords, and if the NT passwords are present, both
655 buffers contain a unicode string.
657 After decrypting the buffers, check the password is correct by
658 matching the old hashed passwords with the passwords in the passdb.
660 ************************************************************/
662 static NTSTATUS check_oem_password(const char *user,
663 uchar password_encrypted_with_lm_hash[516],
664 const uchar old_lm_hash_encrypted[16],
665 uchar password_encrypted_with_nt_hash[516],
666 const uchar old_nt_hash_encrypted[16],
667 struct samu *sampass,
668 char **pp_new_passwd)
670 uchar null_pw[16];
671 uchar null_ntpw[16];
672 uint8 *password_encrypted;
673 const uint8 *encryption_key;
674 const uint8 *lanman_pw, *nt_pw;
675 uint32 acct_ctrl;
676 size_t new_pw_len;
677 uchar new_nt_hash[16];
678 uchar new_lm_hash[16];
679 uchar verifier[16];
680 char no_pw[2];
682 bool nt_pass_set = (password_encrypted_with_nt_hash && old_nt_hash_encrypted);
683 bool lm_pass_set = (password_encrypted_with_lm_hash && old_lm_hash_encrypted);
685 acct_ctrl = pdb_get_acct_ctrl(sampass);
686 #if 0
687 /* I am convinced this check here is wrong, it is valid to
688 * change a password of a user that has a disabled account - gd */
690 if (acct_ctrl & ACB_DISABLED) {
691 DEBUG(2,("check_lanman_password: account %s disabled.\n", user));
692 return NT_STATUS_ACCOUNT_DISABLED;
694 #endif
695 if ((acct_ctrl & ACB_PWNOTREQ) && lp_null_passwords()) {
696 /* construct a null password (in case one is needed */
697 no_pw[0] = 0;
698 no_pw[1] = 0;
699 nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
700 lanman_pw = null_pw;
701 nt_pw = null_pw;
703 } else {
704 /* save pointers to passwords so we don't have to keep looking them up */
705 if (lp_lanman_auth()) {
706 lanman_pw = pdb_get_lanman_passwd(sampass);
707 } else {
708 lanman_pw = NULL;
710 nt_pw = pdb_get_nt_passwd(sampass);
713 if (nt_pw && nt_pass_set) {
714 /* IDEAL Case: passwords are in unicode, and we can
715 * read use the password encrypted with the NT hash
717 password_encrypted = password_encrypted_with_nt_hash;
718 encryption_key = nt_pw;
719 } else if (lanman_pw && lm_pass_set) {
720 /* password may still be in unicode, but use LM hash version */
721 password_encrypted = password_encrypted_with_lm_hash;
722 encryption_key = lanman_pw;
723 } else if (nt_pass_set) {
724 DEBUG(1, ("NT password change supplied for user %s, but we have no NT password to check it with\n",
725 user));
726 return NT_STATUS_WRONG_PASSWORD;
727 } else if (lm_pass_set) {
728 if (lp_lanman_auth()) {
729 DEBUG(1, ("LM password change supplied for user %s, but we have no LanMan password to check it with\n",
730 user));
731 } else {
732 DEBUG(1, ("LM password change supplied for user %s, but we have disabled LanMan authentication\n",
733 user));
735 return NT_STATUS_WRONG_PASSWORD;
736 } else {
737 DEBUG(1, ("password change requested for user %s, but no password supplied!\n",
738 user));
739 return NT_STATUS_WRONG_PASSWORD;
743 * Decrypt the password with the key
745 arcfour_crypt( password_encrypted, encryption_key, 516);
747 if (!decode_pw_buffer(talloc_tos(),
748 password_encrypted,
749 pp_new_passwd,
750 &new_pw_len,
751 nt_pass_set ? CH_UTF16 : CH_DOS)) {
752 return NT_STATUS_WRONG_PASSWORD;
756 * To ensure we got the correct new password, hash it and
757 * use it as a key to test the passed old password.
760 if (nt_pass_set) {
761 /* NT passwords, verify the NT hash. */
763 /* Calculate the MD4 hash (NT compatible) of the password */
764 memset(new_nt_hash, '\0', 16);
765 E_md4hash(*pp_new_passwd, new_nt_hash);
767 if (nt_pw) {
769 * check the NT verifier
771 E_old_pw_hash(new_nt_hash, nt_pw, verifier);
772 if (memcmp(verifier, old_nt_hash_encrypted, 16)) {
773 DEBUG(0, ("check_oem_password: old nt "
774 "password doesn't match.\n"));
775 return NT_STATUS_WRONG_PASSWORD;
778 /* We could check the LM password here, but there is
779 * little point, we already know the password is
780 * correct, and the LM password might not even be
781 * present. */
783 /* Further, LM hash generation algorithms
784 * differ with charset, so we could
785 * incorrectly fail a perfectly valid password
786 * change */
787 #ifdef DEBUG_PASSWORD
788 DEBUG(100,
789 ("check_oem_password: password %s ok\n", *pp_new_passwd));
790 #endif
791 return NT_STATUS_OK;
794 if (lanman_pw) {
796 * check the lm verifier
798 E_old_pw_hash(new_nt_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;
803 #ifdef DEBUG_PASSWORD
804 DEBUG(100,
805 ("check_oem_password: password %s ok\n", *pp_new_passwd));
806 #endif
807 return NT_STATUS_OK;
811 if (lanman_pw && lm_pass_set) {
813 E_deshash(*pp_new_passwd, new_lm_hash);
816 * check the lm verifier
818 E_old_pw_hash(new_lm_hash, lanman_pw, verifier);
819 if (memcmp(verifier, old_lm_hash_encrypted, 16)) {
820 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
821 return NT_STATUS_WRONG_PASSWORD;
824 #ifdef DEBUG_PASSWORD
825 DEBUG(100,
826 ("check_oem_password: password %s ok\n", *pp_new_passwd));
827 #endif
828 return NT_STATUS_OK;
831 /* should not be reached */
832 return NT_STATUS_WRONG_PASSWORD;
835 static bool password_in_history(uint8_t nt_pw[NT_HASH_LEN],
836 uint32_t pw_history_len,
837 const uint8_t *pw_history)
839 static const uint8_t zero_md5_nt_pw[SALTED_MD5_HASH_LEN] = { 0, };
840 int i;
842 dump_data(100, nt_pw, NT_HASH_LEN);
843 dump_data(100, pw_history, PW_HISTORY_ENTRY_LEN * pw_history_len);
845 for (i=0; i<pw_history_len; i++) {
846 uint8_t new_nt_pw_salted_md5_hash[SALTED_MD5_HASH_LEN];
847 const uint8_t *current_salt;
848 const uint8_t *old_nt_pw_salted_md5_hash;
850 current_salt = &pw_history[i*PW_HISTORY_ENTRY_LEN];
851 old_nt_pw_salted_md5_hash = current_salt + PW_HISTORY_SALT_LEN;
853 if (memcmp(zero_md5_nt_pw, old_nt_pw_salted_md5_hash,
854 SALTED_MD5_HASH_LEN) == 0) {
855 /* Ignore zero valued entries. */
856 continue;
859 if (memcmp(zero_md5_nt_pw, current_salt,
860 PW_HISTORY_SALT_LEN) == 0)
863 * New format: zero salt and then plain nt hash.
864 * Directly compare the hashes.
866 if (memcmp(nt_pw, old_nt_pw_salted_md5_hash,
867 SALTED_MD5_HASH_LEN) == 0)
869 return true;
871 } else {
873 * Old format: md5sum of salted nt hash.
874 * Create salted version of new pw to compare.
876 E_md5hash(current_salt, nt_pw, new_nt_pw_salted_md5_hash);
878 if (memcmp(new_nt_pw_salted_md5_hash,
879 old_nt_pw_salted_md5_hash,
880 SALTED_MD5_HASH_LEN) == 0) {
881 return true;
885 return false;
888 /***********************************************************
889 This routine takes the given password and checks it against
890 the password history. Returns True if this password has been
891 found in the history list.
892 ************************************************************/
894 static bool check_passwd_history(struct samu *sampass, const char *plaintext)
896 uchar new_nt_p16[NT_HASH_LEN];
897 const uint8 *nt_pw;
898 const uint8 *pwhistory;
899 uint32 pwHisLen, curr_pwHisLen;
901 pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHisLen);
902 if (pwHisLen == 0) {
903 return False;
906 pwhistory = pdb_get_pw_history(sampass, &curr_pwHisLen);
907 if (!pwhistory || curr_pwHisLen == 0) {
908 return False;
911 /* Only examine the minimum of the current history len and
912 the stored history len. Avoids race conditions. */
913 pwHisLen = MIN(pwHisLen,curr_pwHisLen);
915 nt_pw = pdb_get_nt_passwd(sampass);
917 E_md4hash(plaintext, new_nt_p16);
919 if (!memcmp(nt_pw, new_nt_p16, NT_HASH_LEN)) {
920 DEBUG(10,("check_passwd_history: proposed new password for user %s is the same as the current password !\n",
921 pdb_get_username(sampass) ));
922 return True;
925 if (password_in_history(new_nt_p16, pwHisLen, pwhistory)) {
926 DEBUG(1,("check_passwd_history: proposed new password for "
927 "user %s found in history list !\n",
928 pdb_get_username(sampass) ));
929 return true;
931 return false;
934 /***********************************************************
935 ************************************************************/
937 NTSTATUS check_password_complexity(const char *username,
938 const char *password,
939 enum samPwdChangeReason *samr_reject_reason)
941 TALLOC_CTX *tosctx = talloc_tos();
942 int check_ret;
943 char *cmd;
945 /* Use external script to check password complexity */
946 if ((lp_check_password_script(tosctx) == NULL)
947 || (*(lp_check_password_script(tosctx)) == '\0')) {
948 return NT_STATUS_OK;
951 cmd = talloc_string_sub(tosctx, lp_check_password_script(tosctx), "%u",
952 username);
953 if (!cmd) {
954 return NT_STATUS_PASSWORD_RESTRICTION;
957 check_ret = smbrunsecret(cmd, password);
958 DEBUG(5,("check_password_complexity: check password script (%s) "
959 "returned [%d]\n", cmd, check_ret));
960 TALLOC_FREE(cmd);
962 if (check_ret != 0) {
963 DEBUG(1,("check_password_complexity: "
964 "check password script said new password is not good "
965 "enough!\n"));
966 if (samr_reject_reason) {
967 *samr_reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX;
969 return NT_STATUS_PASSWORD_RESTRICTION;
972 return NT_STATUS_OK;
975 /***********************************************************
976 Code to change the oem password. Changes both the lanman
977 and NT hashes. Old_passwd is almost always NULL.
978 NOTE this function is designed to be called as root. Check the old password
979 is correct before calling. JRA.
980 ************************************************************/
982 static NTSTATUS change_oem_password(struct samu *hnd, const char *rhost,
983 char *old_passwd, char *new_passwd,
984 bool as_root,
985 enum samPwdChangeReason *samr_reject_reason)
987 uint32 min_len;
988 uint32 refuse;
989 TALLOC_CTX *tosctx = talloc_tos();
990 struct passwd *pass = NULL;
991 const char *username = pdb_get_username(hnd);
992 time_t can_change_time = pdb_get_pass_can_change_time(hnd);
993 NTSTATUS status;
995 if (samr_reject_reason) {
996 *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
999 /* check to see if the secdesc has previously been set to disallow */
1000 if (!pdb_get_pass_can_change(hnd)) {
1001 DEBUG(1, ("user %s does not have permissions to change password\n", username));
1002 if (samr_reject_reason) {
1003 *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
1005 return NT_STATUS_ACCOUNT_RESTRICTION;
1008 /* check to see if it is a Machine account and if the policy
1009 * denies machines to change the password. *
1010 * Should we deny also SRVTRUST and/or DOMSTRUST ? .SSS. */
1011 if (pdb_get_acct_ctrl(hnd) & ACB_WSTRUST) {
1012 if (pdb_get_account_policy(PDB_POLICY_REFUSE_MACHINE_PW_CHANGE, &refuse) && refuse) {
1013 DEBUG(1, ("Machine %s cannot change password now, "
1014 "denied by Refuse Machine Password Change policy\n",
1015 username));
1016 if (samr_reject_reason) {
1017 *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
1019 return NT_STATUS_ACCOUNT_RESTRICTION;
1023 /* removed calculation here, because passdb now calculates
1024 based on policy. jmcd */
1025 if ((can_change_time != 0) && (time(NULL) < can_change_time)) {
1026 DEBUG(1, ("user %s cannot change password now, must "
1027 "wait until %s\n", username,
1028 http_timestring(tosctx, can_change_time)));
1029 if (samr_reject_reason) {
1030 *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
1032 return NT_STATUS_ACCOUNT_RESTRICTION;
1035 if (pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, &min_len) && (str_charnum(new_passwd) < min_len)) {
1036 DEBUG(1, ("user %s cannot change password - password too short\n",
1037 username));
1038 DEBUGADD(1, (" account policy min password len = %d\n", min_len));
1039 if (samr_reject_reason) {
1040 *samr_reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT;
1042 return NT_STATUS_PASSWORD_RESTRICTION;
1043 /* return NT_STATUS_PWD_TOO_SHORT; */
1046 if (check_passwd_history(hnd,new_passwd)) {
1047 if (samr_reject_reason) {
1048 *samr_reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
1050 return NT_STATUS_PASSWORD_RESTRICTION;
1053 pass = Get_Pwnam_alloc(tosctx, username);
1054 if (!pass) {
1055 DEBUG(1, ("change_oem_password: Username %s does not exist in system !?!\n", username));
1056 return NT_STATUS_ACCESS_DENIED;
1059 status = check_password_complexity(username, new_passwd, samr_reject_reason);
1060 if (!NT_STATUS_IS_OK(status)) {
1061 TALLOC_FREE(pass);
1062 return status;
1066 * If unix password sync was requested, attempt to change
1067 * the /etc/passwd database first. Return failure if this cannot
1068 * be done.
1070 * This occurs before the oem change, because we don't want to
1071 * update it if chgpasswd failed.
1073 * Conditional on lp_unix_password_sync() because we don't want
1074 * to touch the unix db unless we have admin permission.
1077 if(lp_unix_password_sync() &&
1078 !chgpasswd(username, rhost, pass, old_passwd, new_passwd,
1079 as_root)) {
1080 TALLOC_FREE(pass);
1081 return NT_STATUS_ACCESS_DENIED;
1084 TALLOC_FREE(pass);
1086 if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
1087 return NT_STATUS_ACCESS_DENIED;
1090 /* Now write it into the file. */
1091 return pdb_update_sam_account (hnd);
1094 /***********************************************************
1095 Code to check and change the OEM hashed password.
1096 ************************************************************/
1098 NTSTATUS pass_oem_change(char *user, const char *rhost,
1099 uchar password_encrypted_with_lm_hash[516],
1100 const uchar old_lm_hash_encrypted[16],
1101 uchar password_encrypted_with_nt_hash[516],
1102 const uchar old_nt_hash_encrypted[16],
1103 enum samPwdChangeReason *reject_reason)
1105 char *new_passwd = NULL;
1106 struct samu *sampass = NULL;
1107 NTSTATUS nt_status;
1108 bool ret = false;
1110 if (!(sampass = samu_new(NULL))) {
1111 return NT_STATUS_NO_MEMORY;
1114 become_root();
1115 ret = pdb_getsampwnam(sampass, user);
1116 unbecome_root();
1118 if (ret == false) {
1119 DEBUG(0,("pass_oem_change: getsmbpwnam returned NULL\n"));
1120 TALLOC_FREE(sampass);
1121 return NT_STATUS_NO_SUCH_USER;
1124 nt_status = check_oem_password(user,
1125 password_encrypted_with_lm_hash,
1126 old_lm_hash_encrypted,
1127 password_encrypted_with_nt_hash,
1128 old_nt_hash_encrypted,
1129 sampass,
1130 &new_passwd);
1132 if (!NT_STATUS_IS_OK(nt_status)) {
1133 TALLOC_FREE(sampass);
1134 return nt_status;
1137 /* We've already checked the old password here.... */
1138 become_root();
1139 nt_status = change_oem_password(sampass, rhost, NULL, new_passwd,
1140 True, reject_reason);
1141 unbecome_root();
1143 memset(new_passwd, 0, strlen(new_passwd));
1145 TALLOC_FREE(sampass);
1147 return nt_status;