includes.h: Added feature type USE_GRANTPT for pty code.
[samba.git] / source / smbd / chgpasswd.c
1 /* 
2    Unix SMB/Netbios implementation.
3    Version 1.9.
4    Samba utility functions
5    Copyright (C) Andrew Tridgell 1992-1998
6    
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.
11    
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.
16    
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.
20 */
21
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.
25 */
26
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.
32  *
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.
35  *
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.
40  */
41
42 /*
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.
48    */
49
50 #include "includes.h"
51
52 extern int DEBUGLEVEL;
53
54 #ifdef ALLOW_CHANGE_PASSWORD
55
56 #define MINPASSWDLENGTH 5
57 #define BUFSIZE 512
58
59 static int findpty(char **slave)
60 {
61   int master;
62 #if defined(USE_GRANTPT)
63 #if defined(SVR4) || defined(SUNOS5)
64   extern char *ptsname();
65 #endif /* defined(SVR4) || defined(SUNOS5) */
66 #else /* USE_GRANTPT */
67   static fstring line;
68   void *dirp;
69   char *dpname;
70 #endif /* USE_GRANTPT */
71   
72 #if defined(USE_GRANTPT)
73   if ((master = open("/dev/ptmx", O_RDWR)) >= 1) {
74     grantpt(master);
75     unlockpt(master);
76     *slave = ptsname(master);
77     return (master);
78   }
79 #else /* USE_GRANTPT */
80   fstrcpy( line, "/dev/ptyXX" );
81
82   dirp = OpenDir(-1, "/dev", False);
83   if (!dirp) return(-1);
84   while ((dpname = ReadDirName(dirp)) != NULL) {
85     if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5) {
86       DEBUG(3,("pty: try to open %s, line was %s\n", dpname, line ) );
87       line[8] = dpname[3];
88       line[9] = dpname[4];
89       if ((master = open(line, O_RDWR)) >= 0) {
90         DEBUG(3,("pty: opened %s\n", line ) );
91         line[5] = 't';
92         *slave = line;
93         CloseDir(dirp);
94         return (master);
95       }
96     }
97   }
98   CloseDir(dirp);
99 #endif /* USE_GRANTPT */
100   return (-1);
101 }
102
103 static int dochild(int master,char *slavedev, char *name, char *passwordprogram, BOOL as_root)
104 {
105   int slave;
106   struct termios stermios;
107   struct passwd *pass = Get_Pwnam(name,True);
108   int gid;
109   int uid;
110
111   if(pass == NULL) {
112     DEBUG(0,("dochild: user name %s doesn't exist in the UNIX password database.\n",
113               name));
114     return False;
115   }
116
117   gid = pass->pw_gid;
118   uid = pass->pw_uid;
119 #ifdef USE_SETRES
120   setresuid(0,0,0);
121 #else /* USE_SETRES */
122   setuid(0);
123 #endif /* USE_SETRES */
124
125   /* Start new session - gets rid of controlling terminal. */
126   if (setsid() < 0) {
127     DEBUG(3,("Weirdness, couldn't let go of controlling terminal\n"));
128     return(False);
129   }
130
131   /* Open slave pty and acquire as new controlling terminal. */
132   if ((slave = open(slavedev, O_RDWR)) < 0) {
133     DEBUG(3,("More weirdness, could not open %s\n", 
134              slavedev));
135     return(False);
136   }
137 #if defined(SVR4) || defined(SUNOS5) || defined(SCO)
138   ioctl(slave, I_PUSH, "ptem");
139   ioctl(slave, I_PUSH, "ldterm");
140 #else /* defined(SVR4) || defined(SUNOS5) || defined(SCO) */
141 #if defined(TIOCSCTTY)
142   if (ioctl(slave,TIOCSCTTY,0) <0) {
143      DEBUG(3,("Error in ioctl call for slave pty\n"));
144      /* return(False); */
145   }
146 #endif /* defined(TIOCSCTTY) */
147 #endif /* defined(SVR4) || defined(SUNOS5) || defined(SCO) */
148
149   /* Close master. */
150   close(master);
151
152   /* Make slave stdin/out/err of child. */
153
154   if (dup2(slave, STDIN_FILENO) != STDIN_FILENO) {
155     DEBUG(3,("Could not re-direct stdin\n"));
156     return(False);
157   }
158   if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO) {
159     DEBUG(3,("Could not re-direct stdout\n"));
160     return(False);
161   }
162   if (dup2(slave, STDERR_FILENO) != STDERR_FILENO) {
163     DEBUG(3,("Could not re-direct stderr\n"));
164     return(False);
165   }
166   if (slave > 2) close(slave);
167
168   /* Set proper terminal attributes - no echo, canonical input processing,
169      no map NL to CR/NL on output. */
170
171   if (tcgetattr(0, &stermios) < 0) {
172     DEBUG(3,("could not read default terminal attributes on pty\n"));
173     return(False);
174   }
175   stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
176   stermios.c_lflag |= ICANON;
177   stermios.c_oflag &= ~(ONLCR);
178   if (tcsetattr(0, TCSANOW, &stermios) < 0) {
179     DEBUG(3,("could not set attributes of pty\n"));
180     return(False);
181   }
182
183   /* make us completely into the right uid */
184   if(!as_root) {
185 #ifdef USE_SETRES
186     setresgid(0,0,0);
187     setresuid(0,0,0);
188     setresgid(gid,gid,gid);
189     setresuid(uid,uid,uid);      
190 #else      
191     setuid(0);
192     seteuid(0);
193     setgid(gid);
194     setegid(gid);
195     setuid(uid);
196     seteuid(uid);
197 #endif
198   }
199
200   /* execl() password-change application */
201   if (execl("/bin/sh","sh","-c",passwordprogram,NULL) < 0) {
202     DEBUG(3,("Bad status returned from %s\n",passwordprogram));
203     return(False);
204   }
205   return(True);
206 }
207
208 static int expect(int master,char *expected,char *buf)
209 {
210   int n, m;
211  
212   n = 0;
213   buf[0] = 0;
214   while (1) {
215     if (n >= BUFSIZE-1) {
216       return False;
217     }
218
219     /* allow 4 seconds for some output to appear */
220     m = read_with_timeout(master, buf+n, 1, BUFSIZE-1-n, 4000);
221     if (m < 0) 
222       return False;
223
224     n += m;
225     buf[n] = 0;
226
227     {
228       pstring s1,s2;
229       pstrcpy(s1,buf);
230       pstrcpy(s2,expected);
231       if (do_match(s1, s2, False))
232         return(True);
233     }
234   }
235 }
236
237 static void pwd_sub(char *buf)
238 {
239   string_sub(buf,"\\n","\n");
240   string_sub(buf,"\\r","\r");
241   string_sub(buf,"\\s"," ");
242   string_sub(buf,"\\t","\t");
243 }
244
245 static void writestring(int fd,char *s)
246 {
247   int l;
248   
249   l = strlen (s);
250   write (fd, s, l);
251 }
252
253
254 static int talktochild(int master, char *chatsequence)
255 {
256   char buf[BUFSIZE];
257   int count=0;
258   char *ptr=chatsequence;
259   fstring chatbuf;
260
261   *buf = 0;
262   sleep(1);
263
264   while (next_token(&ptr,chatbuf,NULL)) {
265     BOOL ok=True;
266     count++;
267     pwd_sub(chatbuf);
268     if (!strequal(chatbuf,"."))
269       ok = expect(master,chatbuf,buf);
270
271     if(lp_passwd_chat_debug())
272       DEBUG(100,("talktochild: chatbuf=[%s] responsebuf=[%s]\n",chatbuf,buf));
273
274     if (!ok) {
275       DEBUG(3,("response %d incorrect\n",count));
276       return(False);
277     }
278
279     if (!next_token(&ptr,chatbuf,NULL)) break;
280     pwd_sub(chatbuf);
281     if (!strequal(chatbuf,"."))
282       writestring(master,chatbuf);
283
284     if(lp_passwd_chat_debug())
285       DEBUG(100,("talktochild: sendbuf=[%s]\n",chatbuf));
286   }
287
288   if (count<1) return(False);
289
290   return (True);
291 }
292
293
294 BOOL chat_with_program(char *passwordprogram,char *name,char *chatsequence, BOOL as_root)
295 {
296   char *slavedev;
297   int master;
298   pid_t pid, wpid;
299   int wstat;
300   BOOL chstat;    
301
302   /* allocate a pseudo-terminal device */
303   if ((master = findpty (&slavedev)) < 0) {
304     DEBUG(3,("Cannot Allocate pty for password change: %s",name));
305     return(False);
306   }
307
308   if ((pid = fork()) < 0) {
309     DEBUG(3,("Cannot fork() child for password change: %s",name));
310     return(False);
311   }
312
313   /* we now have a pty */
314   if (pid > 0){                 /* This is the parent process */
315     if ((chstat = talktochild(master, chatsequence)) == False) {
316       DEBUG(3,("Child failed to change password: %s\n",name));
317       kill(pid, SIGKILL); /* be sure to end this process */
318       return(False);
319     }
320     if ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) {
321       DEBUG(3,("The process is no longer waiting!\n\n"));
322       return(False);
323     }
324     if (pid != wpid) {
325       DEBUG(3,("We were waiting for the wrong process ID\n"));  
326       return(False);
327     }
328     if (WIFEXITED(wstat) == 0) {
329       DEBUG(3,("The process exited while we were waiting\n"));
330       return(False);
331     }
332     if (WEXITSTATUS(wstat) != 0) {
333       DEBUG(3,("The status of the process exiting was %d\n", wstat));
334       return(False);
335     }
336     
337   } else {
338     /* CHILD */
339
340     /* make sure it doesn't freeze */
341     alarm(20);
342
343     if(as_root)
344       become_root(False);
345     DEBUG(3,("Dochild for user %s (uid=%d,gid=%d)\n",name,getuid(),getgid()));
346     chstat = dochild(master, slavedev, name, passwordprogram, as_root);
347
348     if(as_root)
349       unbecome_root(False);
350   }
351   DEBUG(3,("Password change %ssuccessful for user %s\n", (chstat?"":"un"), name));
352   return (chstat);
353 }
354
355
356 BOOL chgpasswd(char *name,char *oldpass,char *newpass, BOOL as_root)
357 {
358   pstring passwordprogram;
359   pstring chatsequence;
360   int i;
361   int len;
362
363   strlower(name); 
364   DEBUG(3,("Password change for user: %s\n",name));
365
366 #if DEBUG_PASSWORD
367   DEBUG(100,("Passwords: old=%s new=%s\n",oldpass,newpass)); 
368 #endif
369
370   /* Take the passed information and test it for minimum criteria */
371   /* Minimum password length */
372   if (strlen(newpass) < MINPASSWDLENGTH) /* too short, must be at least MINPASSWDLENGTH */ 
373     {
374       DEBUG(2,("Password Change: %s, New password is shorter than MINPASSWDLENGTH\n",name));
375       return (False);           /* inform the user */
376     }
377   
378   /* Password is same as old password */
379   if (strcmp(oldpass,newpass) == 0) /* don't allow same password */
380     {
381       DEBUG(2,("Password Change: %s, New password is same as old\n",name)); /* log the attempt */
382       return (False);           /* inform the user */
383     }
384
385 #if (defined(PASSWD_PROGRAM) && defined(PASSWD_CHAT))
386   pstrcpy(passwordprogram,PASSWD_PROGRAM);
387   pstrcpy(chatsequence,PASSWD_CHAT);
388 #else
389   pstrcpy(passwordprogram,lp_passwd_program());
390   pstrcpy(chatsequence,lp_passwd_chat());
391 #endif
392
393   if (!*chatsequence) {
394     DEBUG(2,("Null chat sequence - no password changing\n"));
395     return(False);
396   }
397
398   if (!*passwordprogram) {
399     DEBUG(2,("Null password program - no password changing\n"));
400     return(False);
401   }
402
403   /* 
404    * Check the old and new passwords don't contain any control
405    * characters.
406    */
407
408   len = strlen(oldpass); 
409   for(i = 0; i < len; i++) {
410     if(iscntrl(oldpass[i])) {
411       DEBUG(0,("chat_with_program: oldpass contains control characters (disallowed).\n"));
412       return False;
413     }
414   }
415
416   len = strlen(newpass);
417   for(i = 0; i < len; i++) {
418     if(iscntrl(newpass[i])) {
419       DEBUG(0,("chat_with_program: newpass contains control characters (disallowed).\n"));
420       return False;
421     }
422   }
423
424   string_sub(passwordprogram,"%u",name);
425   string_sub(passwordprogram,"%o",oldpass);
426   string_sub(passwordprogram,"%n",newpass);
427
428   string_sub(chatsequence,"%u",name);
429   string_sub(chatsequence,"%o",oldpass);
430   string_sub(chatsequence,"%n",newpass);
431   return(chat_with_program(passwordprogram,name,chatsequence, as_root));
432 }
433
434 #else /* ALLOW_CHANGE_PASSWORD */
435 BOOL chgpasswd(char *name,char *oldpass,char *newpass, BOOL as_root)
436 {
437   DEBUG(0,("Password changing not compiled in (user=%s)\n",name));
438   return(False);
439 }
440 #endif /* ALLOW_CHANGE_PASSWORD */
441
442 /***********************************************************
443  Code to check the lanman hashed password.
444 ************************************************************/
445
446 BOOL check_lanman_password(char *user, unsigned char *pass1, 
447                            unsigned char *pass2, struct smb_passwd **psmbpw)
448 {
449   unsigned char unenc_new_pw[16];
450   unsigned char unenc_old_pw[16];
451   unsigned char null_pw[16];
452   struct smb_passwd *smbpw;
453
454   *psmbpw = NULL;
455
456   become_root(0);
457   smbpw = getsmbpwnam(user);
458   unbecome_root(0);
459
460   if(smbpw == NULL)
461   {
462     DEBUG(0,("check_lanman_password: getsmbpwnam returned NULL\n"));
463     return False;
464   }
465
466   if(smbpw->acct_ctrl & ACB_DISABLED)
467   {
468     DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
469     return False;
470   }
471
472   if((smbpw->smb_passwd == NULL) && (smbpw->acct_ctrl & ACB_PWNOTREQ))
473   {
474     unsigned char no_pw[14];
475     memset(no_pw, '\0', 14);
476     E_P16((uchar *)no_pw, (uchar *)null_pw);
477     smbpw->smb_passwd = null_pw;
478   } else if (smbpw->smb_passwd == NULL) {
479     DEBUG(0,("check_lanman_password: no lanman password !\n"));
480     return False;
481   }
482
483   /* Get the new lanman hash. */
484   D_P16(smbpw->smb_passwd, pass2, unenc_new_pw);
485
486   /* Use this to get the old lanman hash. */
487   D_P16(unenc_new_pw, pass1, unenc_old_pw);
488
489   /* Check that the two old passwords match. */
490   if(memcmp(smbpw->smb_passwd, unenc_old_pw, 16))
491   {
492     DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
493     return False;
494   }
495
496   *psmbpw = smbpw;
497   return True;
498 }
499
500 /***********************************************************
501  Code to change the lanman hashed password.
502  It nulls out the NT hashed password as it will
503  no longer be valid.
504 ************************************************************/
505
506 BOOL change_lanman_password(struct smb_passwd *smbpw, unsigned char *pass1, unsigned char *pass2)
507 {
508   unsigned char unenc_new_pw[16];
509   unsigned char null_pw[16];
510   BOOL ret;
511
512   if(smbpw == NULL)
513   { 
514     DEBUG(0,("change_lanman_password: no smb password entry.\n"));
515     return False;
516   }
517
518   if(smbpw->acct_ctrl & ACB_DISABLED)
519   {
520     DEBUG(0,("change_lanman_password: account %s disabled.\n", smbpw->smb_name));
521     return False;
522   }
523
524   if((smbpw->smb_passwd == NULL) && (smbpw->acct_ctrl & ACB_PWNOTREQ))
525   {
526     unsigned char no_pw[14];
527     memset(no_pw, '\0', 14);
528     E_P16((uchar *)no_pw, (uchar *)null_pw);
529     smbpw->smb_passwd = null_pw;
530   } else if (smbpw->smb_passwd == NULL) {
531     DEBUG(0,("change_lanman_password: no lanman password !\n"));
532     return False;
533   }
534
535   /* Get the new lanman hash. */
536   D_P16(smbpw->smb_passwd, pass2, unenc_new_pw);
537
538   smbpw->smb_passwd = unenc_new_pw;
539   smbpw->smb_nt_passwd = NULL; /* We lose the NT hash. Sorry. */
540
541   /* Now write it into the file. */
542   become_root(0);
543   ret = mod_smbpwd_entry(smbpw,False);
544   unbecome_root(0);
545     
546   return ret;
547 }
548
549 /***********************************************************
550  Code to check the OEM hashed password.
551 ************************************************************/
552
553 BOOL check_oem_password(char *user, unsigned char *data,
554                         struct smb_passwd **psmbpw, char *new_passwd,
555                         int new_passwd_size)
556 {
557   struct smb_passwd *smbpw = NULL;
558   int new_pw_len;
559   fstring upper_case_new_passwd;
560   unsigned char new_p16[16];
561   unsigned char unenc_old_pw[16];
562   unsigned char null_pw[16];
563
564   become_root(0);
565   *psmbpw = smbpw = getsmbpwnam(user);
566   unbecome_root(0);
567
568   if(smbpw == NULL)
569   {
570     DEBUG(0,("check_oem_password: getsmbpwnam returned NULL\n"));
571     return False;
572   }
573
574   if(smbpw->acct_ctrl & ACB_DISABLED)
575   {
576     DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
577     return False;
578   }
579
580   if((smbpw->smb_passwd == NULL) && (smbpw->acct_ctrl & ACB_PWNOTREQ))
581   {
582     unsigned char no_pw[14];
583     memset(no_pw, '\0', 14);
584     E_P16((uchar *)no_pw, (uchar *)null_pw);
585     smbpw->smb_passwd = null_pw;
586   } else if (smbpw->smb_passwd == NULL) {
587     DEBUG(0,("check_oem_password: no lanman password !\n"));
588     return False;
589   }
590
591   /* 
592    * Call the hash function to get the new password.
593    */
594   SamOEMhash( (unsigned char *)data, (unsigned char *)smbpw->smb_passwd, True);
595
596   /* 
597    * The length of the new password is in the last 4 bytes of
598    * the data buffer.
599    */
600   new_pw_len = IVAL(data,512);
601   if(new_pw_len < 0 || new_pw_len > new_passwd_size - 1) {
602     DEBUG(0,("check_oem_password: incorrect password length (%d).\n", new_pw_len));
603     return False;
604   }
605
606   memcpy(new_passwd, &data[512-new_pw_len], new_pw_len);
607   new_passwd[new_pw_len] = '\0';
608
609   /*
610    * To ensure we got the correct new password, hash it and
611    * use it as a key to test the passed old password.
612    */
613
614   memset(upper_case_new_passwd, '\0', sizeof(upper_case_new_passwd));
615   fstrcpy(upper_case_new_passwd, new_passwd);
616   strupper(upper_case_new_passwd);
617
618   E_P16((uchar *)upper_case_new_passwd, new_p16);
619
620   /*
621    * Now use new_p16 as the key to see if the old
622    * password matches.
623    */
624   D_P16(new_p16, &data[516], unenc_old_pw);
625
626   if(memcmp(smbpw->smb_passwd, unenc_old_pw, 16)) {
627     DEBUG(0,("check_oem_password: old password doesn't match.\n"));
628     return False;
629   }
630
631   memset(upper_case_new_passwd, '\0', strlen(upper_case_new_passwd));
632
633   return True;
634 }
635
636 /***********************************************************
637  Code to change the oem password. Changes both the lanman
638  and NT hashes.
639  override = False, normal
640  override = True, override XXXXXXXXXX'd password
641 ************************************************************/
642
643 BOOL change_oem_password(struct smb_passwd *smbpw, char *new_passwd, BOOL override)
644 {
645   int ret;
646   fstring upper_case_new_passwd;
647   unsigned char new_nt_p16[16];
648   unsigned char new_p16[16];
649
650   memset(upper_case_new_passwd, '\0', sizeof(upper_case_new_passwd));
651   fstrcpy(upper_case_new_passwd, new_passwd);
652   strupper(upper_case_new_passwd);
653
654   E_P16((uchar *)upper_case_new_passwd, new_p16);
655
656   smbpw->smb_passwd = new_p16;
657   
658   E_md4hash((uchar *) new_passwd, new_nt_p16);
659   smbpw->smb_nt_passwd = new_nt_p16;
660   
661   /* Now write it into the file. */
662   become_root(0);
663   ret = mod_smbpwd_entry(smbpw,override);
664   unbecome_root(0);
665
666   memset(upper_case_new_passwd, '\0', strlen(upper_case_new_passwd));
667   memset(new_passwd, '\0', strlen(new_passwd));
668
669   return ret;
670 }