2 Unix SMB/Netbios implementation.
4 Samba utility functions
5 Copyright (C) Andrew Tridgell 1992-1998
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 2 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, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 /* fork a child process to exec passwd and write to its
23 * tty to change a users password. This is running as the
24 * user who is attempting to change the password.
28 * This code was copied/borrowed and stolen from various sources.
29 * The primary source was the poppasswd.c from the authors of POPMail. This software
30 * was included as a client to change passwords using the 'passwd' program
31 * on the remote machine.
33 * This routine is called by set_user_password() in password.c only if ALLOW_PASSWORD_CHANGE
34 * is defined in the compiler directives located in the Makefile.
36 * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson
37 * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences
38 * and rights to modify, distribute or incorporate this change to the CAP suite or
39 * using it for any other reason are granted, so long as this disclaimer is left intact.
43 This code was hacked considerably for inclusion in Samba, primarily
44 by Andrew.Tridgell@anu.edu.au. The biggest change was the addition
45 of the "password chat" option, which allows the easy runtime
46 specification of the expected sequence of events to change a
52 extern int DEBUGLEVEL
;
54 #if ALLOW_CHANGE_PASSWORD
56 static int findpty(char **slave
)
63 #if defined(HAVE_GRANTPT)
64 /* Try to open /dev/ptmx. If that fails, fall through to old method. */
65 if ((master
= sys_open("/dev/ptmx", O_RDWR
, 0)) >= 0) {
68 *slave
= (char *)ptsname(master
);
70 DEBUG(0,("findpty: Unable to create master/slave pty pair.\n"));
71 /* Stop fd leak on error. */
75 DEBUG(10, ("findpty: Allocated slave pty %s\n", *slave
));
79 #endif /* HAVE_GRANTPT */
81 fstrcpy( line
, "/dev/ptyXX" );
83 dirp
= OpenDir(NULL
, "/dev", False
);
86 while ((dpname
= ReadDirName(dirp
)) != NULL
) {
87 if (strncmp(dpname
, "pty", 3) == 0 && strlen(dpname
) == 5) {
88 DEBUG(3,("pty: try to open %s, line was %s\n", dpname
, line
) );
91 if ((master
= sys_open(line
, O_RDWR
, 0)) >= 0) {
92 DEBUG(3,("pty: opened %s\n", line
) );
104 static int dochild(int master
,char *slavedev
, char *name
, char *passwordprogram
, BOOL as_root
)
107 struct termios stermios
;
108 struct passwd
*pass
= Get_Pwnam(name
,True
);
113 DEBUG(0,("dochild: user name %s doesn't exist in the UNIX password database.\n",
121 gain_root_privilege();
123 /* Start new session - gets rid of controlling terminal. */
125 DEBUG(3,("Weirdness, couldn't let go of controlling terminal\n"));
129 /* Open slave pty and acquire as new controlling terminal. */
130 if ((slave
= sys_open(slavedev
, O_RDWR
, 0)) < 0) {
131 DEBUG(3,("More weirdness, could not open %s\n",
136 ioctl(slave
, I_PUSH
, "ptem");
137 ioctl(slave
, I_PUSH
, "ldterm");
138 #elif defined(TIOCSCTTY)
139 if (ioctl(slave
,TIOCSCTTY
,0) <0) {
140 DEBUG(3,("Error in ioctl call for slave pty\n"));
148 /* Make slave stdin/out/err of child. */
150 if (dup2(slave
, STDIN_FILENO
) != STDIN_FILENO
) {
151 DEBUG(3,("Could not re-direct stdin\n"));
154 if (dup2(slave
, STDOUT_FILENO
) != STDOUT_FILENO
) {
155 DEBUG(3,("Could not re-direct stdout\n"));
158 if (dup2(slave
, STDERR_FILENO
) != STDERR_FILENO
) {
159 DEBUG(3,("Could not re-direct stderr\n"));
162 if (slave
> 2) close(slave
);
164 /* Set proper terminal attributes - no echo, canonical input processing,
165 no map NL to CR/NL on output. */
167 if (tcgetattr(0, &stermios
) < 0) {
168 DEBUG(3,("could not read default terminal attributes on pty\n"));
171 stermios
.c_lflag
&= ~(ECHO
| ECHOE
| ECHOK
| ECHONL
);
172 stermios
.c_lflag
|= ICANON
;
173 stermios
.c_oflag
&= ~(ONLCR
);
174 if (tcsetattr(0, TCSANOW
, &stermios
) < 0) {
175 DEBUG(3,("could not set attributes of pty\n"));
179 /* make us completely into the right uid */
181 become_user_permanently(uid
, gid
);
184 DEBUG(10, ("Invoking '%s' as password change program.\n", passwordprogram
));
186 /* execl() password-change application */
187 if (execl("/bin/sh","sh","-c",passwordprogram
,NULL
) < 0) {
188 DEBUG(3,("Bad status returned from %s\n",passwordprogram
));
194 static int expect(int master
, char *issue
, char *expected
)
197 int attempts
, timeout
, nread
, len
;
200 for (attempts
= 0; attempts
< 2; attempts
++)
202 if (!strequal(issue
, "."))
204 if (lp_passwd_chat_debug())
205 DEBUG(100, ("expect: sending [%s]\n", issue
));
207 write(master
, issue
, strlen(issue
));
210 if (strequal(expected
, "."))
217 while ((len
= read_with_timeout(master
, buffer
+ nread
, 1,
218 sizeof(buffer
) - nread
- 1, timeout
)) > 0)
223 if ((match
= unix_do_match(buffer
, expected
, False
)))
227 if (lp_passwd_chat_debug())
228 DEBUG(100, ("expect: expected [%s] received [%s]\n",
236 DEBUG(2, ("expect: %s\n", strerror(errno
)));
244 static void pwd_sub(char *buf
)
246 all_string_sub(buf
,"\\n","\n",0);
247 all_string_sub(buf
,"\\r","\r",0);
248 all_string_sub(buf
,"\\s"," ",0);
249 all_string_sub(buf
,"\\t","\t",0);
252 static int talktochild(int master
, char *seq
)
255 fstring issue
, expected
;
259 while (next_token(&seq
, expected
, NULL
, sizeof(expected
)))
264 if (!expect(master
, issue
, expected
))
266 DEBUG(3,("Response %d incorrect\n", count
));
270 if (!next_token(&seq
, issue
, NULL
, sizeof(issue
)))
279 static BOOL
chat_with_program(char *passwordprogram
,char *name
,char *chatsequence
, BOOL as_root
)
287 /* allocate a pseudo-terminal device */
288 if ((master
= findpty (&slavedev
)) < 0) {
289 DEBUG(3,("Cannot Allocate pty for password change: %s\n",name
));
294 * We need to temporarily stop CatchChild from eating
295 * SIGCLD signals as it also eats the exit status code. JRA.
298 CatchChildLeaveStatus();
300 if ((pid
= fork()) < 0) {
301 DEBUG(3,("Cannot fork() child for password change: %s\n",name
));
307 /* we now have a pty */
308 if (pid
> 0){ /* This is the parent process */
309 if ((chstat
= talktochild(master
, chatsequence
)) == False
) {
310 DEBUG(3,("Child failed to change password: %s\n",name
));
311 kill(pid
, SIGKILL
); /* be sure to end this process */
314 while((wpid
= sys_waitpid(pid
, &wstat
, 0)) < 0) {
323 DEBUG(3,("The process is no longer waiting!\n\n"));
330 * Go back to ignoring children.
337 DEBUG(3,("We were waiting for the wrong process ID\n"));
340 if (WIFEXITED(wstat
) == 0) {
341 DEBUG(3,("The process exited while we were waiting\n"));
344 if (WEXITSTATUS(wstat
) != 0) {
345 DEBUG(3,("The status of the process exiting was %d\n", wstat
));
353 * Lose any oplock capabilities.
355 set_process_capability(KERNEL_OPLOCK_CAPABILITY
, False
);
356 set_inherited_process_capability(KERNEL_OPLOCK_CAPABILITY
, False
);
358 /* make sure it doesn't freeze */
363 DEBUG(3,("Dochild for user %s (uid=%d,gid=%d)\n",name
,(int)getuid(),(int)getgid()));
364 chstat
= dochild(master
, slavedev
, name
, passwordprogram
, as_root
);
367 * The child should never return from dochild() ....
370 DEBUG(0,("chat_with_program: Error: dochild() returned %d\n", chstat
));
375 DEBUG(3,("Password change %ssuccessful for user %s\n", (chstat
?"":"un"), name
));
380 BOOL
chgpasswd(char *name
,char *oldpass
,char *newpass
, BOOL as_root
)
382 pstring passwordprogram
;
383 pstring chatsequence
;
388 DEBUG(3,("Password change for user: %s\n",name
));
391 DEBUG(100,("Passwords: old=%s new=%s\n",oldpass
,newpass
));
394 /* Take the passed information and test it for minimum criteria */
395 /* Minimum password length */
396 if (strlen(newpass
) < lp_min_passwd_length()) /* too short, must be at least MINPASSWDLENGTH */
398 DEBUG(0,("Password Change: user %s, New password is shorter than minimum password length = %d\n",
399 name
, lp_min_passwd_length()));
400 return (False
); /* inform the user */
403 /* Password is same as old password */
404 if (strcmp(oldpass
,newpass
) == 0) /* don't allow same password */
406 DEBUG(2,("Password Change: %s, New password is same as old\n",name
)); /* log the attempt */
407 return (False
); /* inform the user */
410 pstrcpy(passwordprogram
,lp_passwd_program());
411 pstrcpy(chatsequence
,lp_passwd_chat());
413 if (!*chatsequence
) {
414 DEBUG(2,("Null chat sequence - no password changing\n"));
418 if (!*passwordprogram
) {
419 DEBUG(2,("Null password program - no password changing\n"));
424 * Check the old and new passwords don't contain any control
428 len
= strlen(oldpass
);
429 for(i
= 0; i
< len
; i
++) {
430 if (iscntrl((int)oldpass
[i
])) {
431 DEBUG(0,("chat_with_program: oldpass contains control characters (disallowed).\n"));
436 len
= strlen(newpass
);
437 for(i
= 0; i
< len
; i
++) {
438 if (iscntrl((int)newpass
[i
])) {
439 DEBUG(0,("chat_with_program: newpass contains control characters (disallowed).\n"));
444 pstring_sub(passwordprogram
,"%u",name
);
445 /* note that we do NOT substitute the %o and %n in the password program
446 as this would open up a security hole where the user could use
447 a new password containing shell escape characters */
449 pstring_sub(chatsequence
,"%u",name
);
450 all_string_sub(chatsequence
,"%o",oldpass
,sizeof(pstring
));
451 all_string_sub(chatsequence
,"%n",newpass
,sizeof(pstring
));
452 return(chat_with_program(passwordprogram
,name
,chatsequence
, as_root
));
455 #else /* ALLOW_CHANGE_PASSWORD */
456 BOOL
chgpasswd(char *name
,char *oldpass
,char *newpass
, BOOL as_root
)
458 DEBUG(0,("Password changing not compiled in (user=%s)\n",name
));
461 #endif /* ALLOW_CHANGE_PASSWORD */
463 /***********************************************************
464 Code to check the lanman hashed password.
465 ************************************************************/
467 BOOL
check_lanman_password(char *user
, uchar
*pass1
,
468 uchar
*pass2
, struct smb_passwd
**psmbpw
)
470 static uchar null_pw
[16];
471 uchar unenc_new_pw
[16];
472 uchar unenc_old_pw
[16];
473 struct smb_passwd
*smbpw
;
478 smbpw
= getsmbpwnam(user
);
483 DEBUG(0,("check_lanman_password: getsmbpwnam returned NULL\n"));
487 if (smbpw
->acct_ctrl
& ACB_DISABLED
)
489 DEBUG(0,("check_lanman_password: account %s disabled.\n", user
));
493 if ((smbpw
->smb_passwd
== NULL
) && (smbpw
->acct_ctrl
& ACB_PWNOTREQ
))
496 memset(no_pw
, '\0', 14);
497 E_P16(no_pw
, null_pw
);
498 smbpw
->smb_passwd
= null_pw
;
499 } else if (smbpw
->smb_passwd
== NULL
) {
500 DEBUG(0,("check_lanman_password: no lanman password !\n"));
504 /* Get the new lanman hash. */
505 D_P16(smbpw
->smb_passwd
, pass2
, unenc_new_pw
);
507 /* Use this to get the old lanman hash. */
508 D_P16(unenc_new_pw
, pass1
, unenc_old_pw
);
510 /* Check that the two old passwords match. */
511 if (memcmp(smbpw
->smb_passwd
, unenc_old_pw
, 16))
513 DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
521 /***********************************************************
522 Code to change the lanman hashed password.
523 It nulls out the NT hashed password as it will
525 ************************************************************/
527 BOOL
change_lanman_password(struct smb_passwd
*smbpw
, uchar
*pass1
, uchar
*pass2
)
529 static uchar null_pw
[16];
530 uchar unenc_new_pw
[16];
535 DEBUG(0,("change_lanman_password: no smb password entry.\n"));
539 if (smbpw
->acct_ctrl
& ACB_DISABLED
)
541 DEBUG(0,("change_lanman_password: account %s disabled.\n", smbpw
->smb_name
));
545 if ((smbpw
->smb_passwd
== NULL
) && (smbpw
->acct_ctrl
& ACB_PWNOTREQ
))
548 memset(no_pw
, '\0', 14);
549 E_P16(no_pw
, null_pw
);
550 smbpw
->smb_passwd
= null_pw
;
551 } else if (smbpw
->smb_passwd
== NULL
) {
552 DEBUG(0,("change_lanman_password: no lanman password !\n"));
556 /* Get the new lanman hash. */
557 D_P16(smbpw
->smb_passwd
, pass2
, unenc_new_pw
);
559 smbpw
->smb_passwd
= unenc_new_pw
;
560 smbpw
->smb_nt_passwd
= NULL
; /* We lose the NT hash. Sorry. */
562 /* Now write it into the file. */
564 ret
= mod_smbpwd_entry(smbpw
,False
);
570 /***********************************************************
571 Code to check and change the OEM hashed password.
572 ************************************************************/
573 BOOL
pass_oem_change(char *user
,
574 uchar
*lmdata
, uchar
*lmhash
,
575 uchar
*ntdata
, uchar
*nthash
)
578 struct smb_passwd
*sampw
;
579 BOOL ret
= check_oem_password( user
, lmdata
, lmhash
, ntdata
, nthash
,
581 new_passwd
, sizeof(new_passwd
));
584 * At this point we have the new case-sensitive plaintext
585 * password in the fstring new_passwd. If we wanted to synchronise
586 * with UNIX passwords we would call a UNIX password changing
587 * function here. However it would have to be done as root
588 * as the plaintext of the old users password is not
592 if ( ret
&& lp_unix_password_sync())
594 ret
= chgpasswd(user
,"", new_passwd
, True
);
599 ret
= change_oem_password( sampw
, new_passwd
, False
);
602 memset(new_passwd
, 0, sizeof(new_passwd
));
607 /***********************************************************
608 Code to check the OEM hashed password.
610 this function ignores the 516 byte nt OEM hashed password
611 but does use the lm OEM password to check the nt hashed-hash.
613 ************************************************************/
614 BOOL
check_oem_password(char *user
,
615 uchar
*lmdata
, uchar
*lmhash
,
616 uchar
*ntdata
, uchar
*nthash
,
617 struct smb_passwd
**psmbpw
, char *new_passwd
,
620 static uchar null_pw
[16];
621 static uchar null_ntpw
[16];
622 struct smb_passwd
*smbpw
= NULL
;
625 uchar unenc_old_ntpw
[16];
627 uchar unenc_old_pw
[16];
630 BOOL nt_pass_set
= (ntdata
!= NULL
&& nthash
!= NULL
);
633 *psmbpw
= smbpw
= getsmbpwnam(user
);
634 unbecome_root(False
);
638 DEBUG(0,("check_oem_password: getsmbpwnam returned NULL\n"));
642 if (smbpw
->acct_ctrl
& ACB_DISABLED
)
644 DEBUG(0,("check_lanman_password: account %s disabled.\n", user
));
648 /* construct a null password (in case one is needed */
651 nt_lm_owf_gen(no_pw
, null_ntpw
, null_pw
);
653 /* check for null passwords */
654 if (smbpw
->smb_passwd
== NULL
)
656 if (smbpw
->acct_ctrl
& ACB_PWNOTREQ
)
658 smbpw
->smb_passwd
= null_pw
;
662 DEBUG(0,("check_oem_password: no lanman password !\n"));
667 if (smbpw
->smb_nt_passwd
== NULL
&& nt_pass_set
)
669 if (smbpw
->acct_ctrl
& ACB_PWNOTREQ
)
671 smbpw
->smb_nt_passwd
= null_pw
;
675 DEBUG(0,("check_oem_password: no ntlm password !\n"));
681 * Call the hash function to get the new password.
683 SamOEMhash( (uchar
*)lmdata
, (uchar
*)smbpw
->smb_passwd
, True
);
686 * The length of the new password is in the last 4 bytes of
690 new_pw_len
= IVAL(lmdata
, 512);
691 if (new_pw_len
< 0 || new_pw_len
> new_passwd_size
- 1)
693 DEBUG(0,("check_oem_password: incorrect password length (%d).\n", new_pw_len
));
700 * nt passwords are in unicode
702 int uni_pw_len
= new_pw_len
;
705 pw
= dos_unistrn2((uint16
*)(&lmdata
[512-uni_pw_len
]), new_pw_len
);
706 memcpy(new_passwd
, pw
, new_pw_len
+1);
710 memcpy(new_passwd
, &lmdata
[512-new_pw_len
], new_pw_len
);
711 new_passwd
[new_pw_len
] = '\0';
715 * To ensure we got the correct new password, hash it and
716 * use it as a key to test the passed old password.
719 nt_lm_owf_gen(new_passwd
, new_ntp16
, new_p16
);
724 * Now use new_p16 as the key to see if the old
727 D_P16(new_p16
, lmhash
, unenc_old_pw
);
729 if (memcmp(smbpw
->smb_passwd
, unenc_old_pw
, 16))
731 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
735 #ifdef DEBUG_PASSWORD
736 DEBUG(100,("check_oem_password: password %s ok\n", new_passwd
));
742 * Now use new_p16 as the key to see if the old
745 D_P16(new_ntp16
, lmhash
, unenc_old_pw
);
746 D_P16(new_ntp16
, nthash
, unenc_old_ntpw
);
748 if (memcmp(smbpw
->smb_passwd
, unenc_old_pw
, 16))
750 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
754 if (memcmp(smbpw
->smb_nt_passwd
, unenc_old_ntpw
, 16))
756 DEBUG(0,("check_oem_password: old nt password doesn't match.\n"));
759 #ifdef DEBUG_PASSWORD
760 DEBUG(100,("check_oem_password: password %s ok\n", new_passwd
));
765 /***********************************************************
766 Code to change the oem password. Changes both the lanman
768 override = False, normal
769 override = True, override XXXXXXXXXX'd password
770 ************************************************************/
772 BOOL
change_oem_password(struct smb_passwd
*smbpw
, char *new_passwd
, BOOL override
)
775 uchar new_nt_p16
[16];
778 nt_lm_owf_gen(new_passwd
, new_nt_p16
, new_p16
);
780 smbpw
->smb_passwd
= new_p16
;
781 smbpw
->smb_nt_passwd
= new_nt_p16
;
783 /* Now write it into the file. */
785 ret
= mod_smbpwd_entry(smbpw
,override
);
788 memset(new_passwd
, '\0', strlen(new_passwd
));
793 /***********************************************************
794 Code to check a plaintext password against smbpasswd entries.
795 ***********************************************************/
797 BOOL
check_plaintext_password(char *user
,char *old_passwd
,
798 int old_passwd_size
, struct smb_passwd
**psmbpw
)
800 struct smb_passwd
*smbpw
= NULL
;
801 uchar old_pw
[16],old_ntpw
[16];
804 *psmbpw
= smbpw
= getsmbpwnam(user
);
805 unbecome_root(False
);
808 DEBUG(0,("check_plaintext_password: getsmbpwnam returned NULL\n"));
812 if (smbpw
->acct_ctrl
& ACB_DISABLED
) {
813 DEBUG(0,("check_plaintext_password: account %s disabled.\n", user
));
817 nt_lm_owf_gen(old_passwd
,old_ntpw
,old_pw
);
819 #ifdef DEBUG_PASSWORD
820 DEBUG(100,("check_plaintext_password: smbpw->smb_nt_passwd \n"));
821 dump_data(100,smbpw
->smb_nt_passwd
,16);
822 DEBUG(100,("check_plaintext_password: old_ntpw \n"));
823 dump_data(100,old_ntpw
,16);
824 DEBUG(100,("check_plaintext_password: smbpw->smb_passwd \n"));
825 dump_data(100,smbpw
->smb_passwd
,16);
826 DEBUG(100,("check_plaintext_password: old_pw\n"));
827 dump_data(100,old_pw
,16);
830 if(memcmp(smbpw
->smb_nt_passwd
,old_ntpw
,16) && memcmp(smbpw
->smb_passwd
,old_pw
,16))