1 /************************************************************************
2  *	Routines related to setting up pipes and filters		*
3  *									*
4  *	Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands	*
5  *	Copyright (c) 1998-2001, Philip Guenther, The United States	*
6  *						of America		*
7  *	#include "../README"						*
8  ************************************************************************/
9 #ifdef RCS
10 static /*const*/char rcsid[]=
11  "$Id: pipes.c,v 1.73 2001/08/27 08:43:59 guenther Exp $";
12 #endif
13 #include "procmail.h"
14 #include "robust.h"
15 #include "shell.h"
16 #include "misc.h"
17 #include "memblk.h"
18 #include "pipes.h"
19 #include "common.h"
20 #include "mcommon.h"
21 #include "foldinfo.h"
22 #include "mailfold.h"
23 #include "goodies.h"
24 #include "variables.h"
25 
26 static const char comma[]=",";
27 pid_t pidchild;
28 volatile time_t alrmtime;
29 volatile int toutflag;
30 static char*lastexec,*backblock;
31 static long backlen;	       /* length of backblock, filter recovery block */
32 static pid_t pidfilt;
33 static int pbackfd[2];			       /* the emergency backpipe :-) */
34 int pipw;
35 
36 #define PRDO	poutfd[0]
37 #define PWRO	poutfd[1]
38 #define PRDI	pinfd[0]
39 #define PWRI	pinfd[1]
40 #define PRDB	pbackfd[0]
41 #define PWRB	pbackfd[1]
42 
inittmout(progname)43 void inittmout(progname)const char*const progname;
44 { lastexec=cstr(lastexec,progname);toutflag=0;
45   alrmtime=timeoutv?time((time_t*)0)+(unsigned)timeoutv:0;
46   alarm((unsigned)timeoutv);
47 }
48 
ftimeout(void)49 void ftimeout P((void))
50 { alarm(0);alrmtime=0;toutflag=1;nlog("Timeout, ");	 /* careful, killing */
51   elog(pidchild>0&&!kill(pidchild,SIGTERM)?"terminating":"was waiting for");
52   logqnl(lastexec);signal(SIGALRM,(void(*)())ftimeout);
53 }
54 
resettmout(void)55 void resettmout P((void))
56 { if(alrmtime)				       /* any need to reset timeout? */
57      alarm((unsigned)(alrmtime=0));			    /* reset timeout */
58 }
59 
stermchild(void)60 static void stermchild P((void))
61 { static const char rescdata[]="Rescue of unfiltered data ";
62   if(pidfilt>0)		    /* don't kill what is not ours, we might be root */
63      kill(pidfilt,SIGTERM);
64   rawnonl=1;				       /* give back the raw contents */
65   if(PWRB<0);						/* previously unset? */
66   else if(dump(PWRB,ft_PIPE,backblock,backlen))	       /* pump data back via */
67      nlog(rescdata),elog("failed\n");			     /* the backpipe */
68   else if(verbose||pwait!=4)		/* are we not looking the other way? */
69      nlog(rescdata),elog("succeeded\n");
70   exit(lexitcode);
71 }
72 
childsetup(void)73 static void childsetup P((void))
74 { lexitcode=EX_UNAVAILABLE;qsignal(SIGTERM,stermchild);
75   qsignal(SIGINT,stermchild);qsignal(SIGHUP,stermchild);
76   qsignal(SIGQUIT,stermchild);shutdesc();
77 }
78 
getstdin(pip)79 static void getstdin(pip)const int pip;
80 { rclose(STDIN);rdup(pip);rclose(pip);
81 }
82 
callnewprog(newname)83 static void callnewprog(newname)const char*const newname;
84 {
85 #ifdef RESTRICT_EXEC
86   if(erestrict&&uid>=RESTRICT_EXEC)
87    { syslog(LOG_ERR,slogstr,"Attempt to execute",newname);
88      nlog("No permission to execute");logqnl(newname);
89      return;
90    }
91 #endif
92   if(sh)					 /* should we start a shell? */
93    { const char*newargv[4];
94      yell(executing,newname);newargv[3]=0;newargv[2]=newname;
95      newargv[1]=shellflags;*newargv=tgetenv(shell);shexec(newargv);
96    }
97   ;{ register const char*p;int argc;
98      argc=1;p=newname;	     /* If no shell, chop up the arguments ourselves */
99      if(verbose)
100       { nlog(executing);elog(oquote);
101 	goto no_1st_comma;
102       }
103      do					     /* show chopped up command line */
104       { if(verbose)
105 	 { elog(comma);
106 no_1st_comma:
107 	   elog(p);
108 	 }
109 	while(*p++);
110 	if(verbose&&p-1==All_args&&crestarg)		  /* any "$@" found? */
111 	 { const char*const*walkargs=restargv;
112 	   goto No_1st_comma;
113 	   do
114 	    { elog(comma);
115 No_1st_comma: elog(*walkargs);					/* expand it */
116 	    }
117 	   while(*++walkargs);
118 	 }
119 	if(p-1==All_args)
120 	   argc+=crestarg-1;			       /* and account for it */
121       }
122      while(argc++,p!=Tmnate);
123      if(verbose)
124 	elog(cquote);				      /* allocate argv array */
125      ;{ const char**newargv;
126 	newargv=malloc(argc*sizeof*newargv);p=newname;argc=0;
127 	do
128 	 { newargv[argc++]=p;
129 	   while(*p++);
130 	   if(p-1==All_args&&crestarg)
131 	    { const char*const*walkargs=restargv;	      /* expand "$@" */
132 	      argc--;
133 	      while(newargv[argc]= *walkargs++)
134 		 argc++;
135 	    }
136 	 }
137 	while(p!=Tmnate);
138 	newargv[argc]=0;shexec(newargv);
139       }
140    }
141 }
142 
pipthrough(line,source,len)143 int pipthrough(line,source,len)char*line,*source;const long len;
144 { int pinfd[2],poutfd[2];char*eq;
145   if(Stdout)
146    { *(eq=strchr(Stdout,'\0')-1)='\0';			     /* chop the '=' */
147      if(!(backblock=getenv(Stdout)))			/* no current value? */
148 	PRDB=PWRB= -1;
149      else
150       { backlen=strlen(backblock);
151 	goto pip;
152       }
153    }
154   else
155 pip: rpipe(pbackfd);
156   rpipe(pinfd);						 /* main pipes setup */
157   if(!(pidchild=sfork()))			/* create a sending procmail */
158    { if(Stdout&&backblock)
159 	backlen=strlen(backblock);
160      else
161 	backblock=source,backlen=len;
162      childsetup();rclose(PRDI);rclose(PRDB);
163      rpipe(poutfd);rclose(STDOUT);
164      if(!(pidfilt=sfork()))				/* create the filter */
165       { rclose(PWRB);rclose(PWRO);rdup(PWRI);rclose(PWRI);getstdin(PRDO);
166 	callnewprog(line);
167       }
168      rclose(PWRI);rclose(PRDO);
169      if(forkerr(pidfilt,line))
170 	rclose(PWRO),stermchild();
171      if(dump(PWRO,ft_PIPE,source,len)&&!ignwerr)	 /* send in the text */
172 	writeerr(line),lexitcode=EX_IOERR,stermchild();	   /* to be filtered */
173      ;{ int excode;	      /* optionally check the exitcode of the filter */
174 	if(pwait&&(excode=waitfor(pidfilt))!=EXIT_SUCCESS)
175 	 { pidfilt=0;
176 	   if(pwait&2)				  /* do we put it on report? */
177 	    { pwait=4;			     /* no, we'll look the other way */
178 	      if(verbose)
179 		 goto perr;
180 	    }
181 	   else
182 perr:	      progerr(line,excode,pwait==4);  /* I'm going to tell my mommy! */
183 	   stermchild();
184 	 }
185       }
186      rclose(PWRB);exit(EXIT_SUCCESS);		  /* allow parent to proceed */
187    }
188   rclose(PWRB);rclose(PWRI);getstdin(PRDI);
189   if(forkerr(pidchild,procmailn))
190      return -1;
191   if(Stdout)
192    { char*name;memblk temp;		    /* ick.  This is inefficient XXX */
193      *eq='=';name=Stdout;Stdout=0;primeStdout(name);free(name);
194      makeblock(&temp,Stdfilled);
195      tmemmove(temp.p,Stdout,Stdfilled);
196      readdyn(&temp,&Stdfilled,Stdfilled+backlen+1);
197      Stdout=realloc(Stdout,Stdfilled+1);
198      tmemmove(Stdout,temp.p,Stdfilled+1);
199      freeblock(&temp);
200      retStdout(Stdout,pwait&&pipw,!backblock);
201      return pipw;
202    }
203   return 0;		    /* we stay behind to read back the filtered text */
204 }
205 
pipin(line,source,len,asgnlastf)206 long pipin(line,source,len,asgnlastf)char*const line;char*source;long len;
207  int asgnlastf;
208 { int poutfd[2];
209 #if 0						     /* not supported (yet?) */
210   if(!sh)					/* shortcut builtin commands */
211    { const char*t1,*t2,*t3;
212      static const char pbuiltin[]="Builtin";
213      t1=strchr(line,'\0')+1;
214      if(!strcmp(test,line))
215       { if(t1!=Tmnate)
216 	 { t2=strchr(t1,'\0')+1;
217 	   if(t2!=Tmnate)
218 	    { t3=strchr(t2,'\0')+1;
219 	      if(t3!=Tmnate&&!strcmp(t2,"=")&&strchr(t3,'\0')==Tmnate-1)
220 	       { int excode;
221 		 if(verbose)
222 		  { nlog(pbuiltin);elog(oquote);elog(test);elog(comma),
223 		 if(!ignwerr)
224 		    writeerr(line);
225 		 else
226 		    len=0;
227 		 if(pwait&&(excode=strcmp(t1,t3)?1:EXIT_SUCCESS)!=EXIT_SUCCESS)
228 		  { if(!(pwait&2)||verbose)	  /* do we put it on report? */
229 		       progerr(line,excode,pwait&2);
230 		    len=1;
231 		  }
232 		 goto builtin;
233 	       }
234 	    }
235 	 }
236       }
237    }
238 #endif
239   rpipe(poutfd);
240   if(!(pidchild=sfork()))				    /* spawn program */
241      rclose(PWRO),shutdesc(),getstdin(PRDO),callnewprog(line);
242   rclose(PRDO);
243   if(forkerr(pidchild,line))
244    { rclose(PWRO);
245      return -1;					    /* dump mail in the pipe */
246    }
247   if((len=dump(PWRO,ft_PIPE,source,len))&&(!ignwerr||(len=0)))
248      writeerr(line);		       /* pipe was shut in our face, get mad */
249   ;{ int excode;			    /* optionally check the exitcode */
250      if(pwait&&(excode=waitfor(pidchild))!=EXIT_SUCCESS)
251       { if(!(pwait&2)||verbose)			  /* do we put it on report? */
252 	   progerr(line,excode,pwait&2);
253 	len=1;
254       }
255    }
256   pidchild=0;
257 builtin:
258   if(!sh)
259      concatenate(line);
260   if(asgnlastf)
261      setlastfolder(line);
262   return len;
263 }
264 
read_read(p,left,data)265 static char*read_read(p,left,data)char*p;long left;void*data;
266 { long got;
267   do
268      if(0>=(got=rread(STDIN,p,left)))				/* read mail */
269 	return p;
270   while(p+=got,left-=got);			/* change listed buffer size */
271   return 0;
272 }
273 
read_cleanup(mb,filledp,origfilled,data)274 static int read_cleanup(mb,filledp,origfilled,data)memblk*mb;
275  long*filledp,origfilled;void*data;
276 { long oldfilled= *(long*)data;
277   if(pidchild>0)
278    { if(PRDB>=0)
279       { getstdin(PRDB);			       /* filter ready, get backpipe */
280 	if(1==rread(STDIN,buf,1))		      /* backup pipe closed? */
281 	 { resizeblock(mb,oldfilled,0);
282 	   mb->p[origfilled]= *buf;
283 	   *filledp=origfilled+1;
284 	   PRDB= -1;pwait=2;		      /* break loop, definitely reap */
285 	   return 1;			       /* filter goofed, rescue data */
286 	 }
287       }
288      if(pwait)
289 	pipw=waitfor(pidchild);		      /* reap your child in any case */
290    }
291   pidchild=0;					/* child must be gone by now */
292   return 0;
293 }
294 
readdyn(mb,filled,oldfilled)295 char*readdyn(mb,filled,oldfilled)memblk*const mb;long*const filled,oldfilled;
296 { return read2blk(mb,filled,&read_read,&read_cleanup,&oldfilled);
297 }
298 
fromprog(name,dest,max)299 char*fromprog(name,dest,max)char*name;char*const dest;size_t max;
300 { int pinfd[2],poutfd[2];int i;char*p;
301   concon('\n');rpipe(pinfd);inittmout(name);
302   if(!(pidchild=sfork()))			/* create a sending procmail */
303    { Stdout=name;childsetup();rclose(PRDI);rpipe(poutfd);rclose(STDOUT);
304      if(!(pidfilt=sfork()))			     /* spawn program/filter */
305 	rclose(PWRO),rdup(PWRI),rclose(PWRI),getstdin(PRDO),callnewprog(name);
306      rclose(PWRI);rclose(PRDO);
307      if(forkerr(pidfilt,name))
308 	rclose(PWRO),stermchild();
309      dump(PWRO,ft_PIPE,themail.p,filled);waitfor(pidfilt);exit(lexitcode);
310    }
311   rclose(PWRI);p=dest;
312   if(!forkerr(pidchild,name))
313    { name=tstrdup(name);
314      while(0<(i=rread(PRDI,p,(int)max))&&(p+=i,max-=i));    /* read its lips */
315      if(0<rread(PRDI,p,1))
316       { nlog("Excessive output quenched from");logqnl(name);
317 	setoverflow();
318       }
319      else
320 	while(--p>=dest&&*p=='\n'); /* trailing newlines should be discarded */
321      rclose(PRDI);free(name);
322      p++;waitfor(pidchild);
323    }
324   else
325      rclose(PRDI);
326   resettmout();
327   pidchild=0;*p='\0';
328   return p;
329 }
330 
exectrap(tp)331 void exectrap(tp)const char*const tp;
332 { int forceret;
333   forceret=setexitcode(*tp);		      /* whether TRAP is set matters */
334   if(*tp)
335    { int poutfd[2];
336      rawnonl=0;					 /* force a trailing newline */
337      metaparse(tp);concon('\n');			     /* expand $TRAP */
338      rpipe(poutfd);inittmout(buf);
339      if(!(pidchild=sfork()))	     /* connect stdout to stderr before exec */
340       { rclose(PWRO);getstdin(PRDO);rclose(STDOUT);rdup(STDERR);
341 	callnewprog(buf);			      /* trap your heart out */
342       }
343      rclose(PRDO);					     /* neat & clean */
344      if(!forkerr(pidchild,buf))
345       { int newret;
346 	dump(PWRO,ft_PIPE,themail.p,filled);  /* try and shove down the mail */
347 	if((newret=waitfor(pidchild))!=EXIT_SUCCESS&&forceret==-2)
348 	   retval=newret;		       /* supersede the return value */
349 	pidchild=0;
350       }
351      else
352 	rclose(PWRO);
353      resettmout();
354    }
355 }
356