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