This commit was manufactured by cvs2svn to create branch 'SAMBA_TNG'.
[Samba.git] / source / smbd / chgpasswd.c
blob6e50c3acb6912a11eebb5580bf23cd427fd8152c
1 /*
2 Unix SMB/Netbios implementation.
3 Version 1.9.
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.
27 /*
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
47 password.
50 #include "includes.h"
52 extern int DEBUGLEVEL;
54 #if ALLOW_CHANGE_PASSWORD
55 #define MINPASSWDLENGTH 5
56 #define BUFSIZE 512
58 static int findpty(char **slave)
60 int master;
61 #ifndef HAVE_GRANTPT
62 static fstring line;
63 DIR *dirp;
64 struct dirent *dentry;
65 char *dpname;
66 #endif /* !HAVE_GRANTPT */
68 #if defined(HAVE_GRANTPT)
69 if ((master = sys_open("/dev/ptmx", O_RDWR, 0)) >= 1) {
70 grantpt(master);
71 unlockpt(master);
72 *slave = ptsname(master);
73 if (*slave == NULL) {
74 DEBUG(0,("findpty: Unable to create master/slave pty pair.\n"));
75 /* Stop fd leak on error. */
76 close(master);
77 return -1;
78 } else {
79 DEBUG(10, ("findpty: Allocated slave pty %s\n", *slave));
80 return (master);
83 #else /* HAVE_GRANTPT */
84 fstrcpy( line, "/dev/ptyXX" );
86 dirp = opendir("/dev");
87 if (!dirp)
88 return(-1);
89 while ((dentry = readdir(dirp)) != NULL) {
90 dpname = dentry->d_name;
91 if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5) {
92 DEBUG(3,("pty: try to open %s, line was %s\n", dpname, line ) );
93 line[8] = dpname[3];
94 line[9] = dpname[4];
95 if ((master = sys_open(line, O_RDWR, 0)) >= 0) {
96 DEBUG(3,("pty: opened %s\n", line ) );
97 line[5] = 't';
98 *slave = line;
99 closedir(dirp);
100 return (master);
104 closedir(dirp);
105 #endif /* HAVE_GRANTPT */
106 return (-1);
109 static int dochild(int master,char *slavedev, const char *_name, char *passwordprogram, BOOL as_root)
111 int slave;
112 struct termios stermios;
113 const struct passwd *pass;
114 int gid;
115 int uid;
117 fstring name;
118 fstrcpy(name, _name);
119 pass = Get_Pwnam(name,True);
121 if (pass == NULL) {
122 DEBUG(0,("dochild: user name %s doesn't exist in the UNIX password database.\n",
123 name));
124 return False;
127 gid = pass->pw_gid;
128 uid = pass->pw_uid;
129 #ifdef HAVE_SETRESUID
130 setresuid(0,0,0);
131 #else
132 setuid(0);
133 #endif
135 /* Start new session - gets rid of controlling terminal. */
136 if (setsid() < 0) {
137 DEBUG(3,("Weirdness, couldn't let go of controlling terminal\n"));
138 return(False);
141 /* Open slave pty and acquire as new controlling terminal. */
142 if ((slave = sys_open(slavedev, O_RDWR, 0)) < 0) {
143 DEBUG(3,("More weirdness, could not open %s\n",
144 slavedev));
145 return(False);
147 #ifdef I_PUSH
148 ioctl(slave, I_PUSH, "ptem");
149 ioctl(slave, I_PUSH, "ldterm");
150 #elif defined(TIOCSCTTY)
151 if (ioctl(slave,TIOCSCTTY,0) <0) {
152 DEBUG(3,("Error in ioctl call for slave pty\n"));
153 /* return(False); */
155 #endif
157 /* Close master. */
158 close(master);
160 /* Make slave stdin/out/err of child. */
162 if (dup2(slave, STDIN_FILENO) != STDIN_FILENO) {
163 DEBUG(3,("Could not re-direct stdin\n"));
164 return(False);
166 if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO) {
167 DEBUG(3,("Could not re-direct stdout\n"));
168 return(False);
170 if (dup2(slave, STDERR_FILENO) != STDERR_FILENO) {
171 DEBUG(3,("Could not re-direct stderr\n"));
172 return(False);
174 if (slave > 2) close(slave);
176 /* Set proper terminal attributes - no echo, canonical input processing,
177 no map NL to CR/NL on output. */
179 if (tcgetattr(0, &stermios) < 0) {
180 DEBUG(3,("could not read default terminal attributes on pty\n"));
181 return(False);
183 stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
184 stermios.c_lflag |= ICANON;
185 stermios.c_oflag &= ~(ONLCR);
186 if (tcsetattr(0, TCSANOW, &stermios) < 0) {
187 DEBUG(3,("could not set attributes of pty\n"));
188 return(False);
191 /* make us completely into the right uid */
192 if (!as_root) {
193 #ifdef HAVE_SETRESUID
194 setresgid(0,0,0);
195 setresuid(0,0,0);
196 setresgid(gid,gid,gid);
197 setresuid(uid,uid,uid);
198 #else
199 setuid(0);
200 seteuid(0);
201 setgid(gid);
202 setegid(gid);
203 setuid(uid);
204 seteuid(uid);
205 #endif
208 DEBUG(10, ("Invoking '%s' as password change program.\n", passwordprogram));
210 /* execl() password-change application */
211 if (execl("/bin/sh","sh","-c",passwordprogram,NULL) < 0) {
212 DEBUG(3,("Bad status returned from %s\n",passwordprogram));
213 return(False);
215 return(True);
218 static int expect(int master,char *expected,char *buf)
220 int n, m;
222 n = 0;
223 buf[0] = 0;
224 while (1) {
225 if (n >= BUFSIZE-1) {
226 return False;
229 /* allow 4 seconds for some output to appear */
230 m = read_with_timeout(master, buf+n, 1, BUFSIZE-1-n, 4000);
231 if (m < 0)
232 return False;
234 n += m;
235 buf[n] = 0;
238 pstring s1,s2;
239 pstrcpy(s1,buf);
240 pstrcpy(s2,expected);
241 if (do_match(s1, s2, False))
242 return(True);
247 static void pwd_sub(char *buf)
249 string_sub(buf,"\\n","\n");
250 string_sub(buf,"\\r","\r");
251 string_sub(buf,"\\s"," ");
252 string_sub(buf,"\\t","\t");
255 static void writestring(int fd,char *s)
257 int l;
259 l = strlen (s);
260 write (fd, s, l);
264 static int talktochild(int master, char *chatsequence)
266 char buf[BUFSIZE];
267 int count=0;
268 char *ptr=chatsequence;
269 fstring chatbuf;
271 *buf = 0;
272 sleep(1);
274 while (next_token(&ptr,chatbuf,NULL,sizeof(chatbuf))) {
275 BOOL ok=True;
276 count++;
277 pwd_sub(chatbuf);
278 if (!strequal(chatbuf,"."))
279 ok = expect(master,chatbuf,buf);
281 if (lp_passwd_chat_debug())
282 DEBUG(100,("talktochild: chatbuf=[%s] responsebuf=[%s]\n",chatbuf,buf));
284 if (!ok) {
285 DEBUG(3,("response %d incorrect\n",count));
286 return(False);
289 if (!next_token(&ptr,chatbuf,NULL,sizeof(chatbuf))) break;
290 pwd_sub(chatbuf);
291 if (!strequal(chatbuf,"."))
292 writestring(master,chatbuf);
294 if (lp_passwd_chat_debug())
295 DEBUG(100,("talktochild: sendbuf=[%s]\n",chatbuf));
298 if (count<1) return(False);
300 return (True);
304 static BOOL chat_with_program(char *passwordprogram,const char *name,char *chatsequence, BOOL as_root)
306 char *slavedev;
307 int master;
308 pid_t pid, wpid;
309 int wstat;
310 BOOL chstat = False;
312 /* allocate a pseudo-terminal device */
313 if ((master = findpty (&slavedev)) < 0) {
314 DEBUG(3,("Cannot Allocate pty for password change: %s\n",name));
315 return(False);
318 if ((pid = fork()) < 0) {
319 DEBUG(3,("Cannot fork() child for password change: %s\n",name));
320 close(master);
321 return(False);
324 /* we now have a pty */
325 if (pid > 0){ /* This is the parent process */
326 if ((chstat = talktochild(master, chatsequence)) == False) {
327 DEBUG(3,("Child failed to change password: %s\n",name));
328 kill(pid, SIGKILL); /* be sure to end this process */
331 if ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) {
332 DEBUG(3,("The process is no longer waiting!\n\n"));
333 close(master);
334 return(False);
337 close(master);
339 if (pid != wpid) {
340 DEBUG(3,("We were waiting for the wrong process ID\n"));
341 return(False);
343 if (WIFEXITED(wstat) == 0) {
344 DEBUG(3,("The process exited while we were waiting\n"));
345 return(False);
347 if (WEXITSTATUS(wstat) != 0) {
348 DEBUG(3,("The status of the process exiting was %d\n", wstat));
349 return(False);
352 } else {
353 /* CHILD */
356 * Lose any oplock capabilities.
358 set_process_capability(KERNEL_OPLOCK_CAPABILITY, False);
359 set_inherited_process_capability(KERNEL_OPLOCK_CAPABILITY, False);
361 /* make sure it doesn't freeze */
362 alarm(20);
364 if (as_root)
365 become_root(False);
366 DEBUG(3,("Dochild for user %s (uid=%d,gid=%d)\n",name,(int)getuid(),(int)getgid()));
367 chstat = dochild(master, slavedev, name, passwordprogram, as_root);
369 if (as_root)
370 unbecome_root(False);
373 if (chstat)
374 DEBUG(3,("Password change %ssuccessful for user %s\n", (chstat?"":"un"), name));
375 return (chstat);
379 BOOL chgpasswd(const char *_name,char *oldpass,char *newpass, BOOL as_root)
381 pstring passwordprogram;
382 pstring chatsequence;
383 int i;
384 int len;
385 fstring name;
387 fstrcpy(name, _name);
388 strlower(name);
389 DEBUG(3,("Password change for user: %s\n",name));
391 #if DEBUG_PASSWORD
392 DEBUG(100,("Passwords: old=%s new=%s\n",oldpass,newpass));
393 #endif
395 /* Take the passed information and test it for minimum criteria */
396 /* Minimum password length */
397 if (strlen(newpass) < MINPASSWDLENGTH) /* too short, must be at least MINPASSWDLENGTH */
399 DEBUG(2,("Password Change: %s, New password is shorter than MINPASSWDLENGTH\n",name));
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"));
415 return(False);
418 if (!*passwordprogram) {
419 DEBUG(2,("Null password program - no password changing\n"));
420 return(False);
424 * Check the old and new passwords don't contain any control
425 * characters.
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"));
432 return False;
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"));
440 return False;
444 string_sub(passwordprogram,"%u",name);
445 all_string_sub(passwordprogram,"%o",oldpass, 0);
446 all_string_sub(passwordprogram,"%n",newpass, 0);
448 string_sub(chatsequence,"%u",name);
449 all_string_sub(chatsequence,"%o",oldpass, sizeof(pstring));
450 all_string_sub(chatsequence,"%n",newpass, sizeof(pstring));
451 return(chat_with_program(passwordprogram,name,chatsequence, as_root));
454 #else /* ALLOW_CHANGE_PASSWORD */
455 BOOL chgpasswd(const char *name,char *oldpass,char *newpass, BOOL as_root)
457 DEBUG(0,("Password changing not compiled in (user=%s)\n",name));
458 return(False);
460 #endif /* ALLOW_CHANGE_PASSWORD */
462 /***********************************************************
463 Code to check the OEM hashed password.
465 this function ignores the 516 byte nt OEM hashed password
466 but does use the lm OEM password to check the nt hashed-hash.
468 ************************************************************/
469 static BOOL check_oem_password(const char *user,
470 const uchar *_lmdata, const uchar *lmhash,
471 const uchar *_ntdata, const uchar *nthash,
472 struct smb_passwd **psmbpw, UNISTR2 *new_passwd)
474 static uchar null_pw[16];
475 static uchar null_ntpw[16];
476 struct smb_passwd *smbpw = NULL;
477 uchar new_ntp16[16];
478 uchar unenc_old_ntpw[16];
479 uchar new_p16[16];
480 uchar unenc_old_pw[16];
481 char no_pw[2];
482 uint32 len;
483 uchar lmdata[516];
484 uchar ntdata[516];
486 BOOL nt_pass_set = (_ntdata != NULL && nthash != NULL);
488 if (_lmdata != NULL)
490 memcpy(lmdata, _lmdata, sizeof(lmdata));
493 if (_ntdata != NULL)
495 memcpy(ntdata, _ntdata, sizeof(ntdata));
498 become_root(False);
499 (*psmbpw) = smbpw = getsmbpwnam(user);
500 unbecome_root(False);
502 if (smbpw == NULL)
504 DEBUG(0,("check_oem_password: getsmbpwnam returned NULL\n"));
505 return False;
508 if (IS_BITS_SET_ALL(smbpw->acct_ctrl, ACB_DISABLED))
510 DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
511 return False;
514 /* construct a null password (in case one is needed */
515 no_pw[0] = 0;
516 no_pw[1] = 0;
517 nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
519 /* check for null passwords */
520 if (smbpw->smb_passwd == NULL)
522 if (smbpw->acct_ctrl & ACB_PWNOTREQ)
524 smbpw->smb_passwd = null_pw;
526 else
528 DEBUG(0,("check_oem_password: no lanman password !\n"));
529 return False;
533 if (smbpw->smb_nt_passwd == NULL && nt_pass_set)
535 if (smbpw->acct_ctrl & ACB_PWNOTREQ)
537 smbpw->smb_nt_passwd = null_pw;
539 else
541 DEBUG(0,("check_oem_password: no ntlm password !\n"));
542 return False;
547 * Call the hash function to get the new password.
549 SamOEMhash( (uchar *)lmdata, (uchar *)smbpw->smb_passwd, True);
551 if (!decode_pw_buffer(lmdata, (char*)new_passwd->buffer, 256, &len))
553 return False;
557 * To ensure we got the correct new password, hash it and
558 * use it as a key to test the passed old password.
561 if (!nt_pass_set)
563 DEBUG(10,("check_oem_password: non-unicode\n"));
564 nt_lm_owf_gen((char*)new_passwd->buffer, new_ntp16, new_p16);
567 * Now use new_p16 as the key to see if the old
568 * password matches.
570 D_P16(new_p16 , lmhash, unenc_old_pw);
572 if (memcmp(smbpw->smb_passwd, unenc_old_pw, 16))
574 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
575 return False;
578 #ifdef DEBUG_PASSWORD
579 DEBUG(100,("check_oem_password: password %s ok\n",
580 (char*)new_passwd->buffer));
581 #endif
582 return True;
584 else
586 new_passwd->uni_max_len = len / 2;
587 new_passwd->uni_str_len = len / 2;
588 nt_lm_owf_genW(new_passwd, new_ntp16, new_p16);
592 * Now use new_p16 as the key to see if the old
593 * password matches.
595 D_P16(new_ntp16, lmhash, unenc_old_pw);
596 D_P16(new_ntp16, nthash, unenc_old_ntpw);
598 #ifdef DEBUG_PASSWORD
599 dump_data(100, lmhash, 16);
600 dump_data(100, unenc_old_pw, 16);
601 dump_data(100, new_ntp16, 16);
602 dump_data(100, smbpw->smb_passwd, 16);
603 #endif
604 if (memcmp(smbpw->smb_passwd, unenc_old_pw, 16))
606 DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
607 return False;
610 if (memcmp(smbpw->smb_nt_passwd, unenc_old_ntpw, 16))
612 DEBUG(0,("check_oem_password: old nt password doesn't match.\n"));
613 return False;
615 #ifdef DEBUG_PASSWORD
616 DEBUG(100,("check_oem_password: password ok\n"));
617 #endif
618 return True;
621 /***********************************************************
622 Code to check and change the OEM hashed password.
623 ************************************************************/
624 BOOL pass_oem_change(const char *user,
625 const uchar *lmdata, const uchar *lmhash,
626 const uchar *ntdata, const uchar *nthash)
628 UNISTR2 new_passwd;
629 struct smb_passwd *sampw = NULL;
630 BOOL ret = check_oem_password( user, lmdata, lmhash, ntdata, nthash,
631 &sampw,
632 &new_passwd);
634 /* now we check to see if we are actually allowed to change the
635 password. */
637 if (ret && (sampw == NULL ||
638 IS_BITS_SET_ALL(sampw->acct_ctrl,ACB_PWLOCK)))
640 if (sampw == NULL)
642 DEBUG(3,("pass_oem_change: account %s not known\n",
643 user));
645 else
647 DEBUG(3,("pass_oem_change: account %s disabled (%x)\n",
648 user, sampw->acct_ctrl));
650 ret = False;
654 * At this point we have the new case-sensitive plaintext
655 * password in the fstring new_passwd. If we wanted to synchronise
656 * with UNIX passwords we would call a UNIX password changing
657 * function here. However it would have to be done as root
658 * as the plaintext of the old users password is not
659 * available. JRA.
662 if ( ret && lp_unix_password_sync())
664 ret = chgpasswd(user,"", (char*)new_passwd.buffer, True);
667 if (ret)
669 ret = change_oem_password( sampw, &new_passwd,
670 ntdata != NULL, False );
673 ZERO_STRUCT(new_passwd);
675 return ret;
678 /***********************************************************
679 Code to change the oem password. Changes both the lanman
680 and NT hashes.
681 override = False, normal
682 override = True, override XXXXXXXXXX'd password
683 ************************************************************/
685 BOOL change_oem_password(struct smb_passwd *smbpw, UNISTR2 *new_passwd,
686 BOOL unicode, BOOL override)
688 int ret;
689 uchar new_nt_p16[16];
690 uchar new_p16[16];
692 DEBUG(100,("change_oem_password: %d\n", __LINE__));
694 if (unicode)
696 nt_lm_owf_genW(new_passwd, new_nt_p16, new_p16);
698 else
700 nt_lm_owf_gen((char*)new_passwd->buffer, new_nt_p16, new_p16);
703 DEBUG(100,("change_oem_password: %d\n", __LINE__));
704 dbgflush();
706 smbpw->smb_passwd = new_p16;
707 smbpw->smb_nt_passwd = new_nt_p16;
709 DEBUG(100,("change_oem_password: %d\n", __LINE__));
710 dbgflush();
712 /* Now write it into the file. */
713 become_root(0);
714 ret = mod_smbpwd_entry(smbpw,override);
715 unbecome_root(0);
717 ZERO_STRUCTP(new_passwd);
719 return ret;
722 /****************************************************************************
723 update the encrypted smbpasswd file from the plaintext username and password
724 *****************************************************************************/
725 BOOL update_smbpassword_file(const char *user, const char *password)
727 struct smb_passwd *smbpw;
728 UNISTR2 newpw;
729 BOOL ret;
731 become_root(0);
732 smbpw = getsmbpwnam(user);
733 unbecome_root(0);
735 if(smbpw == NULL)
737 DEBUG(0,("getsmbpwnam returned NULL\n"));
738 return False;
741 make_unistr2(&newpw, password, password != NULL ? strlen(password) : 0);
743 /* Here, the flag is one, because we want to ignore the
744 XXXXXXX'd out password */
745 ret = change_oem_password( smbpw, &newpw, True, True);
746 if (!ret)
748 DEBUG(3,("change_oem_password returned False\n"));
751 ZERO_STRUCT(newpw);
753 return ret;