a cleanup of the receive_smb() usage, adding timeouts in some places
[samba.git] / source3 / smbd / chgpasswd.c
1 /* fork a child process to exec passwd and write to its
2 * tty to change a users password. This is running as the
3 * user who is attempting to change the password.
4 */
5
6 /* 
7  * This code was copied/borrowed and stolen from various sources.
8  * The primary source was the poppasswd.c from the authors of POPMail. This software
9  * was included as a client to change passwords using the 'passwd' program
10  * on the remote machine.
11  *
12  * This routine is called by set_user_password() in password.c only if ALLOW_PASSWORD_CHANGE
13  * is defined in the compiler directives located in the Makefile.
14  *
15  * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson
16  * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences
17  * and rights to modify, distribute or incorporate this change to the CAP suite or
18  * using it for any other reason are granted, so long as this disclaimer is left intact.
19  */
20
21 /*
22    This code was hacked considerably for inclusion in Samba, primarily
23    by Andrew.Tridgell@anu.edu.au. The biggest change was the addition
24    of the "password chat" option, which allows the easy runtime
25    specification of the expected sequence of events to change a
26    password.
27    */
28
29 #include "includes.h"
30
31 extern int DEBUGLEVEL;
32
33 #ifdef ALLOW_CHANGE_PASSWORD
34
35 #define MINPASSWDLENGTH 5
36 #define BUFSIZE 512
37
38 static int findpty(char **slave)
39 {
40   int master;
41 #ifdef SVR4
42   extern char *ptsname();
43 #else
44   static char line[12] = "/dev/ptyXX";
45   void *dirp;
46   char *dpname;
47 #endif
48   
49 #ifdef SVR4
50   if ((master = open("/dev/ptmx", O_RDWR)) >= 1) {
51     grantpt(master);
52     unlockpt(master);
53     *slave = ptsname(master);
54     return (master);
55   }
56 #else
57   dirp = OpenDir("/dev");
58   if (!dirp) return(-1);
59   while ((dpname = ReadDirName(dirp)) != NULL) {
60     if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5) {
61       line[8] = dpname[3];
62       line[9] = dpname[4];
63       if ((master = open(line, O_RDWR)) >= 0) {
64         line[5] = 't';
65         *slave = line;
66         CloseDir(dirp);
67         return (master);
68       }
69     }
70   }
71   CloseDir(dirp);
72 #endif
73   return (-1);
74 }
75
76 static int dochild(int master,char *slavedev, char *name, char *passwordprogram)
77 {
78   int slave;
79   struct termios stermios;
80   struct passwd *pass = Get_Pwnam(name,True);
81   int gid = pass->pw_gid;
82   int uid = pass->pw_uid;
83
84 #ifdef USE_SETRES
85   setresuid(0,0,0);
86 #else
87   setuid(0);
88 #endif
89
90   /* Start new session - gets rid of controlling terminal. */
91   if (setsid() < 0) {
92     DEBUG(3,("Weirdness, couldn't let go of controlling terminal\n"));
93     return(False);
94   }
95
96   /* Open slave pty and acquire as new controlling terminal. */
97   if ((slave = open(slavedev, O_RDWR)) < 0) {
98     DEBUG(3,("More weirdness, could not open %s\n", 
99              slavedev));
100     return(False);
101   }
102 #ifdef SVR4
103   ioctl(slave, I_PUSH, "ptem");
104   ioctl(slave, I_PUSH, "ldterm");
105 #else
106   if (ioctl(slave,TIOCSCTTY,0) <0) {
107      DEBUG(3,("Error in ioctl call for slave pty\n"));
108      /* return(False); */
109   }
110 #endif
111
112   /* Close master. */
113   close(master);
114
115   /* Make slave stdin/out/err of child. */
116
117   if (dup2(slave, STDIN_FILENO) != STDIN_FILENO) {
118     DEBUG(3,("Could not re-direct stdin\n"));
119     return(False);
120   }
121   if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO) {
122     DEBUG(3,("Could not re-direct stdout\n"));
123     return(False);
124   }
125   if (dup2(slave, STDERR_FILENO) != STDERR_FILENO) {
126     DEBUG(3,("Could not re-direct stderr\n"));
127     return(False);
128   }
129   if (slave > 2) close(slave);
130
131   /* Set proper terminal attributes - no echo, canonical input processing,
132      no map NL to CR/NL on output. */
133
134   if (tcgetattr(0, &stermios) < 0) {
135     DEBUG(3,("could not read default terminal attributes on pty\n"));
136     return(False);
137   }
138   stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
139   stermios.c_lflag |= ICANON;
140   stermios.c_oflag &= ~(ONLCR);
141   if (tcsetattr(0, TCSANOW, &stermios) < 0) {
142     DEBUG(3,("could not set attributes of pty\n"));
143     return(False);
144   }
145
146   /* make us completely into the right uid */
147 #ifdef USE_SETRES
148   setresgid(0,0,0);
149   setresuid(0,0,0);
150   setresgid(gid,gid,gid);
151   setresuid(uid,uid,uid);      
152 #else      
153   setuid(0);
154   seteuid(0);
155   setgid(gid);
156   setegid(gid);
157   setuid(uid);
158   seteuid(uid);
159 #endif
160
161   /* execl() password-change application */
162   if (execl("/bin/sh","sh","-c",passwordprogram,NULL) < 0) {
163     DEBUG(3,("Bad status returned from %s\n",passwordprogram));
164     return(False);
165   }
166   return(True);
167 }
168
169 static int expect(int master,char *expected,char *buf)
170 {
171   int n, m;
172  
173   n = 0;
174   buf[0] = 0;
175   while (1) {
176     if (n >= BUFSIZE-1) {
177       return False;
178     }
179
180     /* allow 4 seconds for some output to appear */
181     m = read_with_timeout(master, buf+n, 1, BUFSIZE-1-n, 4000, True);
182     if (m < 0) 
183       return False;
184
185     n += m;
186     buf[n] = 0;
187
188     {
189       pstring s1,s2;
190       strcpy(s1,buf);
191       strcpy(s2,expected);
192       if (do_match(s1, s2, False))
193         return(True);
194     }
195   }
196 }
197
198 static void pwd_sub(char *buf)
199 {
200   string_sub(buf,"\\n","\n");
201   string_sub(buf,"\\r","\r");
202   string_sub(buf,"\\s"," ");
203   string_sub(buf,"\\t","\t");
204 }
205
206 static void writestring(int fd,char *s)
207 {
208   int l;
209   
210   l = strlen (s);
211   write (fd, s, l);
212 }
213
214
215 static int talktochild(int master, char *chatsequence)
216 {
217   char buf[BUFSIZE];
218   int count=0;
219   char *ptr=chatsequence;
220   fstring chatbuf;
221
222   *buf = 0;
223   sleep(1);
224
225   while (next_token(&ptr,chatbuf,NULL)) {
226     BOOL ok=True;
227     count++;
228     pwd_sub(chatbuf);
229     if (!strequal(chatbuf,"."))
230       ok = expect(master,chatbuf,buf);
231
232 #if DEBUG_PASSWORD
233       DEBUG(100,("chatbuf=[%s] responsebuf=[%s]\n",chatbuf,buf));
234 #endif      
235
236     if (!ok) {
237       DEBUG(3,("response %d incorrect\n",count));
238       return(False);
239     }
240
241     if (!next_token(&ptr,chatbuf,NULL)) break;
242     pwd_sub(chatbuf);
243     if (!strequal(chatbuf,"."))
244       writestring(master,chatbuf);
245
246 #if DEBUG_PASSWORD
247     DEBUG(100,("sendbuf=[%s]\n",chatbuf));
248 #endif      
249   }
250
251   if (count<1) return(False);
252
253   return (True);
254 }
255
256
257 BOOL chat_with_program(char *passwordprogram,char *name,char *chatsequence)
258 {
259   char *slavedev;
260   int master;
261   pid_t pid, wpid;
262   int wstat;
263   BOOL chstat;    
264
265   /* allocate a pseudo-terminal device */
266   if ((master = findpty (&slavedev)) < 0) {
267     DEBUG(3,("Cannot Allocate pty for password change: %s",name));
268     return(False);
269   }
270
271   if ((pid = fork()) < 0) {
272     DEBUG(3,("Cannot fork() child for password change: %s",name));
273     return(False);
274   }
275
276   /* we now have a pty */
277   if (pid > 0){                 /* This is the parent process */
278     if ((chstat = talktochild(master, chatsequence)) == False) {
279       DEBUG(3,("Child failed to change password: %s\n",name));
280       kill(pid, SIGKILL); /* be sure to end this process */
281       return(False);
282     }
283     if ((wpid = waitpid(pid, &wstat, 0)) < 0) {
284       DEBUG(3,("The process is no longer waiting!\n\n"));
285       return(False);
286     }
287     if (pid != wpid) {
288       DEBUG(3,("We were waiting for the wrong process ID\n"));  
289       return(False);
290     }
291     if (WIFEXITED(wstat) == 0) {
292       DEBUG(3,("The process exited while we were waiting\n"));
293       return(False);
294     }
295     if (WEXITSTATUS(wstat) != 0) {
296       DEBUG(3,("The status of the process exiting was %d\n", wstat));
297       return(False);
298     }
299     
300   } else {
301     /* CHILD */
302
303     /* make sure it doesn't freeze */
304     alarm(20);
305
306     DEBUG(3,("Dochild for user %s (uid=%d,gid=%d)\n",name,getuid(),getgid()));
307     chstat = dochild(master, slavedev, name, passwordprogram);
308   }
309   DEBUG(3,("Password change %ssuccessful for user %s\n", (chstat?"":"un"), name));
310   return (chstat);
311 }
312
313
314 BOOL chgpasswd(char *name,char *oldpass,char *newpass)
315 {
316   pstring passwordprogram;
317   pstring chatsequence;
318
319   strlower(name); 
320   DEBUG(3,("Password change for user: %s\n",name));
321
322 #if DEBUG_PASSWORD
323   DEBUG(100,("Passwords: old=%s new=%s\n",oldpass,newpass)); 
324 #endif
325
326   /* Take the passed information and test it for minimum criteria */
327   /* Minimum password length */
328   if (strlen(newpass) < MINPASSWDLENGTH) /* too short, must be at least MINPASSWDLENGTH */ 
329     {
330       DEBUG(2,("Password Change: %s, New password is shorter than MINPASSWDLENGTH\n",name));
331       return (False);           /* inform the user */
332     }
333   
334   /* Password is same as old password */
335   if (strcmp(oldpass,newpass) == 0) /* don't allow same password */
336     {
337       DEBUG(2,("Password Change: %s, New password is same as old\n",name)); /* log the attempt */
338       return (False);           /* inform the user */
339     }
340
341 #if (defined(PASSWD_PROGRAM) && defined(PASSWD_CHAT))
342   strcpy(passwordprogram,PASSWD_PROGRAM);
343   strcpy(chatsequence,PASSWD_CHAT);
344 #else
345   strcpy(passwordprogram,lp_passwd_program());
346   strcpy(chatsequence,lp_passwd_chat());
347 #endif
348
349   if (!*chatsequence) {
350     DEBUG(2,("Null chat sequence - no password changing\n"));
351     return(False);
352   }
353
354   if (!*passwordprogram) {
355     DEBUG(2,("Null password program - no password changing\n"));
356     return(False);
357   }
358
359   string_sub(passwordprogram,"%u",name);
360   string_sub(passwordprogram,"%o",oldpass);
361   string_sub(passwordprogram,"%n",newpass);
362
363   string_sub(chatsequence,"%u",name);
364   string_sub(chatsequence,"%o",oldpass);
365   string_sub(chatsequence,"%n",newpass);
366   return(chat_with_program(passwordprogram,name,chatsequence));
367 }
368
369 #else
370 BOOL chgpasswd(char *name,char *oldpass,char *newpass)
371 {
372   DEBUG(0,("Password changing not compiled in (user=%s)\n",name));
373   return(False);
374 }
375 #endif