"For I have laboured mightily on Luke's code, and hath broken
[kai/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)
102 {
103   int slave;
104   struct termios stermios;
105   struct passwd *pass = Get_Pwnam(name,True);
106   int gid = pass->pw_gid;
107   int uid = pass->pw_uid;
108
109 #ifdef USE_SETRES
110   setresuid(0,0,0);
111 #else /* USE_SETRES */
112   setuid(0);
113 #endif /* USE_SETRES */
114
115   /* Start new session - gets rid of controlling terminal. */
116   if (setsid() < 0) {
117     DEBUG(3,("Weirdness, couldn't let go of controlling terminal\n"));
118     return(False);
119   }
120
121   /* Open slave pty and acquire as new controlling terminal. */
122   if ((slave = open(slavedev, O_RDWR)) < 0) {
123     DEBUG(3,("More weirdness, could not open %s\n", 
124              slavedev));
125     return(False);
126   }
127 #if defined(SVR4) || defined(SUNOS5) || defined(SCO)
128   ioctl(slave, I_PUSH, "ptem");
129   ioctl(slave, I_PUSH, "ldterm");
130 #else /* defined(SVR4) || defined(SUNOS5) || defined(SCO) */
131   if (ioctl(slave,TIOCSCTTY,0) <0) {
132      DEBUG(3,("Error in ioctl call for slave pty\n"));
133      /* return(False); */
134   }
135 #endif /* defined(SVR4) || defined(SUNOS5) || defined(SCO) */
136
137   /* Close master. */
138   close(master);
139
140   /* Make slave stdin/out/err of child. */
141
142   if (dup2(slave, STDIN_FILENO) != STDIN_FILENO) {
143     DEBUG(3,("Could not re-direct stdin\n"));
144     return(False);
145   }
146   if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO) {
147     DEBUG(3,("Could not re-direct stdout\n"));
148     return(False);
149   }
150   if (dup2(slave, STDERR_FILENO) != STDERR_FILENO) {
151     DEBUG(3,("Could not re-direct stderr\n"));
152     return(False);
153   }
154   if (slave > 2) close(slave);
155
156   /* Set proper terminal attributes - no echo, canonical input processing,
157      no map NL to CR/NL on output. */
158
159   if (tcgetattr(0, &stermios) < 0) {
160     DEBUG(3,("could not read default terminal attributes on pty\n"));
161     return(False);
162   }
163   stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
164   stermios.c_lflag |= ICANON;
165   stermios.c_oflag &= ~(ONLCR);
166   if (tcsetattr(0, TCSANOW, &stermios) < 0) {
167     DEBUG(3,("could not set attributes of pty\n"));
168     return(False);
169   }
170
171   /* make us completely into the right uid */
172 #ifdef USE_SETRES
173   setresgid(0,0,0);
174   setresuid(0,0,0);
175   setresgid(gid,gid,gid);
176   setresuid(uid,uid,uid);      
177 #else      
178   setuid(0);
179   seteuid(0);
180   setgid(gid);
181   setegid(gid);
182   setuid(uid);
183   seteuid(uid);
184 #endif
185
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));
189     return(False);
190   }
191   return(True);
192 }
193
194 static int expect(int master,char *expected,char *buf)
195 {
196   int n, m;
197  
198   n = 0;
199   buf[0] = 0;
200   while (1) {
201     if (n >= BUFSIZE-1) {
202       return False;
203     }
204
205     /* allow 4 seconds for some output to appear */
206     m = read_with_timeout(master, buf+n, 1, BUFSIZE-1-n, 4000);
207     if (m < 0) 
208       return False;
209
210     n += m;
211     buf[n] = 0;
212
213     {
214       pstring s1,s2;
215       pstrcpy(s1,buf);
216       pstrcpy(s2,expected);
217       if (do_match(s1, s2, False))
218         return(True);
219     }
220   }
221 }
222
223 static void pwd_sub(char *buf)
224 {
225   string_sub(buf,"\\n","\n");
226   string_sub(buf,"\\r","\r");
227   string_sub(buf,"\\s"," ");
228   string_sub(buf,"\\t","\t");
229 }
230
231 static void writestring(int fd,char *s)
232 {
233   int l;
234   
235   l = strlen (s);
236   write (fd, s, l);
237 }
238
239
240 static int talktochild(int master, char *chatsequence)
241 {
242   char buf[BUFSIZE];
243   int count=0;
244   char *ptr=chatsequence;
245   fstring chatbuf;
246
247   *buf = 0;
248   sleep(1);
249
250   while (next_token(&ptr,chatbuf,NULL)) {
251     BOOL ok=True;
252     count++;
253     pwd_sub(chatbuf);
254     if (!strequal(chatbuf,"."))
255       ok = expect(master,chatbuf,buf);
256
257 #if DEBUG_PASSWORD
258       DEBUG(100,("chatbuf=[%s] responsebuf=[%s]\n",chatbuf,buf));
259 #endif      
260
261     if (!ok) {
262       DEBUG(3,("response %d incorrect\n",count));
263       return(False);
264     }
265
266     if (!next_token(&ptr,chatbuf,NULL)) break;
267     pwd_sub(chatbuf);
268     if (!strequal(chatbuf,"."))
269       writestring(master,chatbuf);
270
271 #if DEBUG_PASSWORD
272     DEBUG(100,("sendbuf=[%s]\n",chatbuf));
273 #endif      
274   }
275
276   if (count<1) return(False);
277
278   return (True);
279 }
280
281
282 BOOL chat_with_program(char *passwordprogram,char *name,char *chatsequence)
283 {
284   char *slavedev;
285   int master;
286   pid_t pid, wpid;
287   int wstat;
288   BOOL chstat;    
289
290   /* allocate a pseudo-terminal device */
291   if ((master = findpty (&slavedev)) < 0) {
292     DEBUG(3,("Cannot Allocate pty for password change: %s",name));
293     return(False);
294   }
295
296   if ((pid = fork()) < 0) {
297     DEBUG(3,("Cannot fork() child for password change: %s",name));
298     return(False);
299   }
300
301   /* we now have a pty */
302   if (pid > 0){                 /* This is the parent process */
303     if ((chstat = talktochild(master, chatsequence)) == False) {
304       DEBUG(3,("Child failed to change password: %s\n",name));
305       kill(pid, SIGKILL); /* be sure to end this process */
306       return(False);
307     }
308     if ((wpid = sys_waitpid(pid, &wstat, 0)) < 0) {
309       DEBUG(3,("The process is no longer waiting!\n\n"));
310       return(False);
311     }
312     if (pid != wpid) {
313       DEBUG(3,("We were waiting for the wrong process ID\n"));  
314       return(False);
315     }
316     if (WIFEXITED(wstat) == 0) {
317       DEBUG(3,("The process exited while we were waiting\n"));
318       return(False);
319     }
320     if (WEXITSTATUS(wstat) != 0) {
321       DEBUG(3,("The status of the process exiting was %d\n", wstat));
322       return(False);
323     }
324     
325   } else {
326     /* CHILD */
327
328     /* make sure it doesn't freeze */
329     alarm(20);
330
331     DEBUG(3,("Dochild for user %s (uid=%d,gid=%d)\n",name,getuid(),getgid()));
332     chstat = dochild(master, slavedev, name, passwordprogram);
333   }
334   DEBUG(3,("Password change %ssuccessful for user %s\n", (chstat?"":"un"), name));
335   return (chstat);
336 }
337
338
339 BOOL chgpasswd(char *name,char *oldpass,char *newpass)
340 {
341   pstring passwordprogram;
342   pstring chatsequence;
343
344   strlower(name); 
345   DEBUG(3,("Password change for user: %s\n",name));
346
347 #if DEBUG_PASSWORD
348   DEBUG(100,("Passwords: old=%s new=%s\n",oldpass,newpass)); 
349 #endif
350
351   /* Take the passed information and test it for minimum criteria */
352   /* Minimum password length */
353   if (strlen(newpass) < MINPASSWDLENGTH) /* too short, must be at least MINPASSWDLENGTH */ 
354     {
355       DEBUG(2,("Password Change: %s, New password is shorter than MINPASSWDLENGTH\n",name));
356       return (False);           /* inform the user */
357     }
358   
359   /* Password is same as old password */
360   if (strcmp(oldpass,newpass) == 0) /* don't allow same password */
361     {
362       DEBUG(2,("Password Change: %s, New password is same as old\n",name)); /* log the attempt */
363       return (False);           /* inform the user */
364     }
365
366 #if (defined(PASSWD_PROGRAM) && defined(PASSWD_CHAT))
367   pstrcpy(passwordprogram,PASSWD_PROGRAM);
368   pstrcpy(chatsequence,PASSWD_CHAT);
369 #else
370   pstrcpy(passwordprogram,lp_passwd_program());
371   pstrcpy(chatsequence,lp_passwd_chat());
372 #endif
373
374   if (!*chatsequence) {
375     DEBUG(2,("Null chat sequence - no password changing\n"));
376     return(False);
377   }
378
379   if (!*passwordprogram) {
380     DEBUG(2,("Null password program - no password changing\n"));
381     return(False);
382   }
383
384   string_sub(passwordprogram,"%u",name);
385   string_sub(passwordprogram,"%o",oldpass);
386   string_sub(passwordprogram,"%n",newpass);
387
388   string_sub(chatsequence,"%u",name);
389   string_sub(chatsequence,"%o",oldpass);
390   string_sub(chatsequence,"%n",newpass);
391   return(chat_with_program(passwordprogram,name,chatsequence));
392 }
393
394 #else
395 BOOL chgpasswd(char *name,char *oldpass,char *newpass)
396 {
397   DEBUG(0,("Password changing not compiled in (user=%s)\n",name));
398   return(False);
399 }
400 #endif
401
402 /***********************************************************
403  Code to check the lanman hashed password.
404 ************************************************************/
405
406 BOOL check_lanman_password(char *user, unsigned char *pass1, 
407                            unsigned char *pass2, struct smb_passwd **psmbpw)
408 {
409   unsigned char unenc_new_pw[16];
410   unsigned char unenc_old_pw[16];
411   struct smb_passwd *smbpw;
412
413   *psmbpw = NULL;
414
415   become_root(0);
416   smbpw = get_smbpwd_entry(user, 0);
417   unbecome_root(0);
418
419   if(smbpw == NULL)
420   {
421     DEBUG(0,("check_lanman_password: get_smbpwd_entry returned NULL\n"));
422     return False;
423   }
424
425   if(smbpw->acct_ctrl & ACB_DISABLED)
426   {
427     DEBUG(0,("check_lanman_password: account %s disabled.\n", user));
428     return False;
429   }
430
431   if(smbpw->smb_passwd == NULL)
432   {
433     DEBUG(0,("check_lanman_password: no lanman password !\n"));
434     return False;
435   }
436
437   /* Get the new lanman hash. */
438   D_P16(smbpw->smb_passwd, pass2, unenc_new_pw);
439
440   /* Use this to get the old lanman hash. */
441   D_P16(unenc_new_pw, pass1, unenc_old_pw);
442
443   /* Check that the two old passwords match. */
444   if(memcmp(smbpw->smb_passwd, unenc_old_pw, 16))
445   {
446     DEBUG(0,("check_lanman_password: old password doesn't match.\n"));
447     return False;
448   }
449
450   *psmbpw = smbpw;
451   return True;
452 }
453
454 /***********************************************************
455  Code to change the lanman hashed password.
456  It nulls out the NT hashed password as it will
457  no longer be valid.
458 ************************************************************/
459
460 BOOL change_lanman_password(struct smb_passwd *smbpw, unsigned char *pass1, unsigned char *pass2)
461 {
462   unsigned char unenc_new_pw[16];
463   BOOL ret;
464
465   if(smbpw == NULL)
466   { 
467     DEBUG(0,("change_lanman_password: get_smbpwd_entry returned NULL\n"));
468     return False;
469   }
470
471   if(smbpw->acct_ctrl & ACB_DISABLED)
472   {
473     DEBUG(0,("change_lanman_password: account %s disabled.\n", smbpw->smb_name));
474     return False;
475   }
476
477   if(smbpw->smb_passwd == NULL)
478   {
479     DEBUG(0,("change_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   smbpw->smb_passwd = unenc_new_pw;
487   smbpw->smb_nt_passwd = NULL; /* We lose the NT hash. Sorry. */
488
489   /* Now write it into the file. */
490   become_root(0);
491   ret = mod_smbpwd_entry(smbpw);
492   unbecome_root(0);
493     
494   return ret;
495 }