2 Unix SMB/CIFS implementation.
3 Samba utility functions
4 Copyright (C) Andrew Tridgell 1992-1998
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 /* fork a child process to exec passwd and write to its
22 * tty to change a users password. This is running as the
23 * user who is attempting to change the password.
27 * This code was copied/borrowed and stolen from various sources.
28 * The primary source was the poppasswd.c from the authors of POPMail. This software
29 * was included as a client to change passwords using the 'passwd' program
30 * on the remote machine.
32 * This routine is called by set_user_password() in password.c only if ALLOW_PASSWORD_CHANGE
33 * is defined in the compiler directives located in the Makefile.
35 * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson
36 * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences
37 * and rights to modify, distribute or incorporate this change to the CAP suite or
38 * using it for any other reason are granted, so long as this disclaimer is left intact.
42 This code was hacked considerably for inclusion in Samba, primarily
43 by Andrew.Tridgell@anu.edu.au. The biggest change was the addition
44 of the "password chat" option, which allows the easy runtime
45 specification of the expected sequence of events to change a
51 extern struct passdb_ops pdb_ops;
53 static BOOL check_oem_password(const char *user,
54 uchar * lmdata, const uchar * lmhash,
55 const uchar * ntdata, const uchar * nthash,
56 SAM_ACCOUNT **hnd, char *new_passwd,
59 #if ALLOW_CHANGE_PASSWORD
61 static int findpty(char **slave)
68 #if defined(HAVE_GRANTPT)
69 /* Try to open /dev/ptmx. If that fails, fall through to old method. */
70 if ((master = sys_open("/dev/ptmx", O_RDWR, 0)) >= 0)
74 *slave = (char *)ptsname(master);
78 ("findpty: Unable to create master/slave pty pair.\n"));
79 /* Stop fd leak on error. */
86 ("findpty: Allocated slave pty %s\n", *slave));
90 #endif /* HAVE_GRANTPT */
92 fstrcpy(line, "/dev/ptyXX");
94 dirp = opendir("/dev");
97 while ((dpname = readdirname(dirp)) != NULL)
99 if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5)
102 ("pty: try to open %s, line was %s\n", dpname,
106 if ((master = sys_open(line, O_RDWR, 0)) >= 0)
108 DEBUG(3, ("pty: opened %s\n", line));
120 static int dochild(int master, const char *slavedev, const struct passwd *pass,
121 const char *passwordprogram, BOOL as_root)
124 struct termios stermios;
131 ("dochild: user doesn't exist in the UNIX password database.\n"));
138 gain_root_privilege();
140 /* Start new session - gets rid of controlling terminal. */
144 ("Weirdness, couldn't let go of controlling terminal\n"));
148 /* Open slave pty and acquire as new controlling terminal. */
149 if ((slave = sys_open(slavedev, O_RDWR, 0)) < 0)
151 DEBUG(3, ("More weirdness, could not open %s\n", slavedev));
155 ioctl(slave, I_PUSH, "ptem");
156 ioctl(slave, I_PUSH, "ldterm");
157 #elif defined(TIOCSCTTY)
158 if (ioctl(slave, TIOCSCTTY, 0) < 0)
160 DEBUG(3, ("Error in ioctl call for slave pty\n"));
168 /* Make slave stdin/out/err of child. */
170 if (dup2(slave, STDIN_FILENO) != STDIN_FILENO)
172 DEBUG(3, ("Could not re-direct stdin\n"));
175 if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO)
177 DEBUG(3, ("Could not re-direct stdout\n"));
180 if (dup2(slave, STDERR_FILENO) != STDERR_FILENO)
182 DEBUG(3, ("Could not re-direct stderr\n"));
188 /* Set proper terminal attributes - no echo, canonical input processing,
189 no map NL to CR/NL on output. */
191 if (tcgetattr(0, &stermios) < 0)
194 ("could not read default terminal attributes on pty\n"));
197 stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
198 stermios.c_lflag |= ICANON;
199 stermios.c_oflag &= ~(ONLCR);
200 if (tcsetattr(0, TCSANOW, &stermios) < 0)
202 DEBUG(3, ("could not set attributes of pty\n"));
206 /* make us completely into the right uid */
209 become_user_permanently(uid, gid);
213 ("Invoking '%s' as password change program.\n",
216 /* execl() password-change application */
217 if (execl("/bin/sh", "sh", "-c", passwordprogram, NULL) < 0)
219 DEBUG(3, ("Bad status returned from %s\n", passwordprogram));
225 static int expect(int master, char *issue, char *expected)
228 int attempts, timeout, nread, len;
231 for (attempts = 0; attempts < 2; attempts++) {
232 if (!strequal(issue, ".")) {
233 if (lp_passwd_chat_debug())
234 DEBUG(100, ("expect: sending [%s]\n", issue));
236 if ((len = write(master, issue, strlen(issue))) != strlen(issue)) {
237 DEBUG(2,("expect: (short) write returned %d\n", len ));
242 if (strequal(expected, "."))
249 while ((len = read_with_timeout(master, buffer + nread, 1,
250 sizeof(buffer) - nread - 1,
256 /* Eat leading/trailing whitespace before match. */
258 pstrcpy( str, buffer);
259 trim_string( str, " ", " ");
261 if ((match = (unix_wild_match(expected, str) == 0)))
266 if (lp_passwd_chat_debug())
267 DEBUG(100, ("expect: expected [%s] received [%s] match %s\n",
268 expected, buffer, match ? "yes" : "no" ));
274 DEBUG(2, ("expect: %s\n", strerror(errno)));
279 DEBUG(10,("expect: returning %s\n", match ? "True" : "False" ));
283 static void pwd_sub(char *buf)
285 all_string_sub(buf, "\\n", "\n", 0);
286 all_string_sub(buf, "\\r", "\r", 0);
287 all_string_sub(buf, "\\s", " ", 0);
288 all_string_sub(buf, "\\t", "\t", 0);
291 static int talktochild(int master, char *seq)
294 fstring issue, expected;
298 while (next_token(&seq, expected, NULL, sizeof(expected)))
303 if (!expect(master, issue, expected))
305 DEBUG(3, ("Response %d incorrect\n", count));
309 if (!next_token(&seq, issue, NULL, sizeof(issue)))
314 if (!strequal(issue, ".")) {
315 /* we have one final issue to send */
316 fstrcpy(expected, ".");
317 if (!expect(master, issue, expected))
324 static BOOL chat_with_program(char *passwordprogram, struct passwd *pass,
325 char *chatsequence, BOOL as_root)
336 ("chat_with_program: user doesn't exist in the UNIX password database.\n"));
340 /* allocate a pseudo-terminal device */
341 if ((master = findpty(&slavedev)) < 0)
344 ("Cannot Allocate pty for password change: %s\n",
350 * We need to temporarily stop CatchChild from eating
351 * SIGCLD signals as it also eats the exit status code. JRA.
354 CatchChildLeaveStatus();
356 if ((pid = sys_fork()) < 0)
359 ("Cannot fork() child for password change: %s\n",
366 /* we now have a pty */
368 { /* This is the parent process */
369 if ((chstat = talktochild(master, chatsequence)) == False)
372 ("Child failed to change password: %s\n",
374 kill(pid, SIGKILL); /* be sure to end this process */
377 while ((wpid = sys_waitpid(pid, &wstat, 0)) < 0)
389 DEBUG(3, ("The process is no longer waiting!\n\n"));
396 * Go back to ignoring children.
405 ("We were waiting for the wrong process ID\n"));
408 if (WIFEXITED(wstat) == 0)
411 ("The process exited while we were waiting\n"));
414 if (WEXITSTATUS(wstat) != 0)
417 ("The status of the process exiting was %d\n",
428 * Lose any oplock capabilities.
430 oplock_set_capability(False, False);
432 /* make sure it doesn't freeze */
439 ("Dochild for user %s (uid=%d,gid=%d)\n", pass->pw_name,
440 (int)getuid(), (int)getgid()));
442 dochild(master, slavedev, pass, passwordprogram,
449 * The child should never return from dochild() ....
453 ("chat_with_program: Error: dochild() returned %d\n",
460 ("Password change %ssuccessful for user %s\n",
461 (chstat ? "" : "un"), pass->pw_name));
466 BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root)
468 pstring passwordprogram;
469 pstring chatsequence;
475 DEBUG(3, ("Password change for user: %s\n", name));
478 DEBUG(100, ("Passwords: old=%s new=%s\n", oldpass, newpass));
481 /* Take the passed information and test it for minimum criteria */
482 /* Minimum password length */
483 if (strlen(newpass) < lp_min_passwd_length()) {
484 /* too short, must be at least MINPASSWDLENGTH */
485 DEBUG(0, ("Password Change: user %s, New password is shorter than minimum password length = %d\n",
486 name, lp_min_passwd_length()));
487 return (False); /* inform the user */
490 /* Password is same as old password */
491 if (strcmp(oldpass, newpass) == 0) {
492 /* don't allow same password */
493 DEBUG(2, ("Password Change: %s, New password is same as old\n", name)); /* log the attempt */
494 return (False); /* inform the user */
498 * Check the old and new passwords don't contain any control
502 len = strlen(oldpass);
503 for (i = 0; i < len; i++) {
504 if (iscntrl((int)oldpass[i])) {
506 ("chat_with_program: oldpass contains control characters (disallowed).\n"));
511 len = strlen(newpass);
512 for (i = 0; i < len; i++) {
513 if (iscntrl((int)newpass[i])) {
515 ("chat_with_program: newpass contains control characters (disallowed).\n"));
520 pass = Get_Pwnam(name);
523 if (lp_pam_password_change()) {
530 ret = smb_pam_passchange(pass->pw_name, oldpass, newpass);
532 ret = smb_pam_passchange(name, oldpass, newpass);
542 /* A non-PAM password change just doen't make sense without a valid local user */
547 ("chgpasswd: user %s doesn't exist in the UNIX password database.\n",
552 pstrcpy(passwordprogram, lp_passwd_program());
553 pstrcpy(chatsequence, lp_passwd_chat());
555 if (!*chatsequence) {
556 DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
560 if (!*passwordprogram) {
561 DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
566 /* The password program *must* contain the user name to work. Fail if not. */
567 if (strstr(passwordprogram, "%u") == NULL) {
568 DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
569 the string %%u, and the given string %s does not.\n", passwordprogram ));
574 pstring_sub(passwordprogram, "%u", name);
575 /* note that we do NOT substitute the %o and %n in the password program
576 as this would open up a security hole where the user could use
577 a new password containing shell escape characters */
579 pstring_sub(chatsequence, "%u", name);
580 all_string_sub(chatsequence, "%o", oldpass, sizeof(pstring));
581 all_string_sub(chatsequence, "%n", newpass, sizeof(pstring));
582 return (chat_with_program
583 (passwordprogram, pass, chatsequence, as_root));
586 #else /* ALLOW_CHANGE_PASSWORD */
588 BOOL chgpasswd(const char *name, const char *oldpass, const char *newpass, BOOL as_root)
590 DEBUG(0, ("Password changing not compiled in (user=%s)\n", name));
593 #endif /* ALLOW_CHANGE_PASSWORD */
595 /***********************************************************
596 Code to check the lanman hashed password.
597 ************************************************************/
599 BOOL check_lanman_password(char *user, uchar * pass1,
600 uchar * pass2, SAM_ACCOUNT **hnd)
602 uchar unenc_new_pw[16];
603 uchar unenc_old_pw[16];
604 SAM_ACCOUNT *sampass = NULL;
606 const uint8 *lanman_pw;
610 ret = pdb_getsampwnam(sampass, user);
614 DEBUG(0,("check_lanman_password: getsampwnam returned NULL\n"));
615 pdb_free_sam(&sampass);
619 acct_ctrl = pdb_get_acct_ctrl (sampass);
620 lanman_pw = pdb_get_lanman_passwd (sampass);
622 if (acct_ctrl & ACB_DISABLED) {
623 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
624 pdb_free_sam(&sampass);
628 if (lanman_pw == NULL) {
629 if (acct_ctrl & ACB_PWNOTREQ) {
630 /* this saves the pointer for the caller */
634 DEBUG(0, ("check_lanman_password: no lanman password !\n"));
635 pdb_free_sam(&sampass);
640 /* Get the new lanman hash. */
641 D_P16(lanman_pw, pass2, unenc_new_pw);
643 /* Use this to get the old lanman hash. */
644 D_P16(unenc_new_pw, pass1, unenc_old_pw);
646 /* Check that the two old passwords match. */
647 if (memcmp(lanman_pw, unenc_old_pw, 16)) {
648 DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
649 pdb_free_sam(&sampass);
653 /* this saves the pointer for the caller */
658 /***********************************************************
659 Code to change the lanman hashed password.
660 It nulls out the NT hashed password as it will
662 ************************************************************/
664 BOOL change_lanman_password(SAM_ACCOUNT *sampass, uchar * pass1,
667 static uchar null_pw[16];
668 uchar unenc_new_pw[16];
673 if (sampass == NULL) {
674 DEBUG(0,("change_lanman_password: no smb password entry.\n"));
678 acct_ctrl = pdb_get_acct_ctrl(sampass);
679 pwd = pdb_get_lanman_passwd(sampass);
681 if (acct_ctrl & ACB_DISABLED) {
682 DEBUG(0,("change_lanman_password: account %s disabled.\n",
683 pdb_get_username(sampass)));
688 if (acct_ctrl & ACB_PWNOTREQ) {
690 memset(no_pw, '\0', 14);
691 E_P16(no_pw, null_pw);
693 /* Get the new lanman hash. */
694 D_P16(null_pw, pass2, unenc_new_pw);
696 DEBUG(0,("change_lanman_password: no lanman password !\n"));
700 /* Get the new lanman hash. */
701 D_P16(pwd, pass2, unenc_new_pw);
704 if (!pdb_set_lanman_passwd(sampass, unenc_new_pw)) {
708 if (!pdb_set_nt_passwd (sampass, NULL)) {
709 return False; /* We lose the NT hash. Sorry. */
712 if (!pdb_set_pass_changed_now (sampass)) {
713 pdb_free_sam(&sampass);
714 /* Not quite sure what this one qualifies as, but this will do */
718 /* Now flush the sam_passwd struct to persistent storage */
720 ret = pdb_update_sam_account (sampass);
726 /***********************************************************
727 Code to check and change the OEM hashed password.
728 ************************************************************/
729 BOOL pass_oem_change(char *user,
730 uchar * lmdata, uchar * lmhash,
731 uchar * ntdata, uchar * nthash)
734 const char *unix_user;
735 SAM_ACCOUNT *sampass = NULL;
736 BOOL ret = check_oem_password(user, lmdata, lmhash, ntdata, nthash,
737 &sampass, new_passwd, sizeof(new_passwd));
740 * At this point we have the new case-sensitive plaintext
741 * password in the fstring new_passwd. If we wanted to synchronise
742 * with UNIX passwords we would call a UNIX password changing
743 * function here. However it would have to be done as root
744 * as the plaintext of the old users password is not
748 unix_user = pdb_get_username(sampass);
750 if ((ret) && (unix_user) && (*unix_user) && lp_unix_password_sync())
751 ret = chgpasswd(unix_user, "", new_passwd, True);
754 ret = change_oem_password(sampass, new_passwd);
756 memset(new_passwd, 0, sizeof(new_passwd));
758 pdb_free_sam(&sampass);
763 /***********************************************************
764 Code to check the OEM hashed password.
766 this function ignores the 516 byte nt OEM hashed password
767 but does use the lm OEM password to check the nt hashed-hash.
769 ************************************************************/
770 static BOOL check_oem_password(const char *user,
771 uchar * lmdata, const uchar * lmhash,
772 const uchar * ntdata, const uchar * nthash,
773 SAM_ACCOUNT **hnd, char *new_passwd,
776 static uchar null_pw[16];
777 static uchar null_ntpw[16];
778 SAM_ACCOUNT *sampass = NULL;
779 const uint8 *lanman_pw, *nt_pw;
783 uchar unenc_old_ntpw[16];
785 uchar unenc_old_pw[16];
789 BOOL nt_pass_set = (ntdata != NULL && nthash != NULL);
791 pdb_init_sam(&sampass);
794 ret = pdb_getsampwnam(sampass, user);
798 DEBUG(0, ("check_oem_password: getsmbpwnam returned NULL\n"));
804 acct_ctrl = pdb_get_acct_ctrl(sampass);
806 if (acct_ctrl & ACB_DISABLED) {
807 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
811 /* construct a null password (in case one is needed */
814 nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
816 /* save pointers to passwords so we don't have to keep looking them up */
817 lanman_pw = pdb_get_lanman_passwd(sampass);
818 nt_pw = pdb_get_nt_passwd (sampass);
820 /* check for null passwords */
821 if (lanman_pw == NULL) {
822 if (!(acct_ctrl & ACB_PWNOTREQ)) {
823 DEBUG(0,("check_oem_password: no lanman password !\n"));
828 if (pdb_get_nt_passwd(sampass) == NULL && nt_pass_set) {
829 if (!(acct_ctrl & ACB_PWNOTREQ)) {
830 DEBUG(0,("check_oem_password: no ntlm password !\n"));
836 * Call the hash function to get the new password.
838 SamOEMhash( lmdata, lanman_pw, 516);
841 * The length of the new password is in the last 4 bytes of
845 new_pw_len = IVAL(lmdata, 512);
846 if (new_pw_len < 0 || new_pw_len > new_passwd_size - 1) {
847 DEBUG(0,("check_oem_password: incorrect password length (%d).\n", new_pw_len));
853 * nt passwords are in unicode
855 pull_ucs2(NULL, new_passwd,
856 (const smb_ucs2_t *)&lmdata[512 - new_pw_len],
857 new_passwd_size, new_pw_len, 0);
859 memcpy(new_passwd, &lmdata[512 - new_pw_len], new_pw_len);
860 new_passwd[new_pw_len] = 0;
864 * To ensure we got the correct new password, hash it and
865 * use it as a key to test the passed old password.
868 nt_lm_owf_gen(new_passwd, new_ntp16, new_p16);
873 * Now use new_p16 as the key to see if the old
876 D_P16(new_p16, lmhash, unenc_old_pw);
878 if (memcmp(lanman_pw, unenc_old_pw, 16))
880 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
884 #ifdef DEBUG_PASSWORD
886 ("check_oem_password: password %s ok\n", new_passwd));
892 * Now use new_p16 as the key to see if the old
895 D_P16(new_ntp16, lmhash, unenc_old_pw);
896 D_P16(new_ntp16, nthash, unenc_old_ntpw);
898 if (memcmp(lanman_pw, unenc_old_pw, 16))
900 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
904 if (memcmp(nt_pw, unenc_old_ntpw, 16))
906 DEBUG(0,("check_oem_password: old nt password doesn't match.\n"));
909 #ifdef DEBUG_PASSWORD
910 DEBUG(100, ("check_oem_password: password %s ok\n", new_passwd));
915 /***********************************************************
916 Code to change the oem password. Changes both the lanman
918 ************************************************************/
920 BOOL change_oem_password(SAM_ACCOUNT *hnd, char *new_passwd)
924 if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
928 /* Now write it into the file. */
930 ret = pdb_update_sam_account (hnd);