1/************************************************************************
2 *	procmail - The autonomous mail processor			*
3 *									*
4 *	It has been designed to be able to be run suid root and (in	*
5 *	case your mail spool area is *not* world writable) sgid		*
6 *	mail (or daemon), without creating security holes.		*
7 *									*
8 *	Seems to be perfect.						*
9 *									*
10 *	Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands	*
11 *	Copyright (c) 1999-2001, Philip Guenther, The United States	*
12 *							of America	*
13 *	#include "../README"						*
14 ************************************************************************/
15#ifdef RCS
16static /*const*/char rcsid[]=
17 "$Id: procmail.c,v 1.183 2001/08/31 04:57:36 guenther Exp $";
18#endif
19#include "../patchlevel.h"
20#include "procmail.h"
21#include "acommon.h"
22#include "sublib.h"
23#include "robust.h"
24#include "shell.h"
25#include "misc.h"
26#include "memblk.h"
27#include "pipes.h"
28#include "common.h"
29#include "cstdio.h"
30#include "exopen.h"
31#include "mcommon.h"
32#include "goodies.h"
33#include "locking.h"
34#include "mailfold.h"
35#include "lastdirsep.h"
36#include "authenticate.h"
37#include "lmtp.h"
38#include "foldinfo.h"
39#include "variables.h"
40#include "comsat.h"
41#include "from.h"
42
43static const char*const nullp,exflags[]=RECFLAGS,drcfile[]="Rcfile:",
44 pmusage[]=PM_USAGE,*etcrc=ETCRC,misrecpt[]="Missing recipient\n",
45 extrns[]="Extraneous ",ignrd[]=" ignored\n",pardir[]=chPARDIR,
46 defspath[]=DEFSPATH,defmaildir[]=DEFmaildir;
47char*buf,*buf2,*loclock;
48const char shell[]="SHELL",lockfile[]="LOCKFILE",newline[]="\n",binsh[]=BinSh,
49 unexpeof[]="Unexpected EOL\n",*const*gargv,*const*restargv= &nullp,*sgetcp,
50 pmrc[]=PROCMAILRC,*rcfile,dirsep[]=DIRSEP,devnull[]=DevNull,empty[]="",
51 lgname[]="LOGNAME",executing[]="Executing",oquote[]=" \"",cquote[]="\"\n",
52 procmailn[]="procmail",whilstwfor[]=" whilst waiting for ",home[]="HOME",
53 host[]="HOST",*defdeflock=empty,*argv0=empty,curdir[]={chCURDIR,'\0'},
54 slogstr[]="%s \"%s\"",conflicting[]="Conflicting ",orgmail[]="ORGMAIL",
55 insufprivs[]="Insufficient privileges\n",defpath[]=DEFPATH,
56 exceededlb[]="Exceeded LINEBUF\n",errwwriting[]="Error while writing to",
57 Version[]=VERSION;
58int retval=EX_CANTCREAT,retvl2=EXIT_SUCCESS,sh,pwait,rc= -1,
59 privileged=priv_START,lexitcode=EXIT_SUCCESS,ignwerr,crestarg,savstdout,
60 berkeley,mailfilter,erestrict,Deliverymode,ifdepth;   /* depth of outermost */
61struct dyna_array ifstack;
62size_t linebuf=mx(DEFlinebuf,1024/*STRLEN(systm_mbox)<<1*/);
63volatile int nextexit,lcking;		       /* if termination is imminent */
64pid_t thepid;
65long filled,lastscore;	       /* the length of the mail, and the last score */
66memblk themail;							 /* the mail */
67char*thebody;					  /* the body of the message */
68uid_t uid;
69gid_t gid,sgid;
70
71static auth_identity*savepass(spass,uid)auth_identity*const spass;
72 const uid_t uid;
73{ const auth_identity*tpass;
74  if(auth_filledid(spass)&&auth_whatuid(spass)==uid)
75     goto ret;
76  if(tpass=auth_finduid(uid,0))				  /* save by copying */
77   { auth_copyid(spass,tpass);
78ret: return spass;
79   }
80  return (auth_identity*)0;
81}
82
83#define rct_ABSOLUTE	0			    /* rctypes for tryopen() */
84#define rct_CURRENT	1
85#define rct_DEFAULT	2
86
87#define rcs_DELIVERED	1		     /* rc exit codes for mainloop() */
88#define rcs_EOF		2
89#define rcs_HOST	3
90
91static void
92 usage P((void));
93static int
94 tryopen P((const int delay_setid,const int rctype,const int dowarning)),
95 mainloop P((void));
96
97int main(argc,argv)int argc;const char*const argv[];
98{ register char*chp,*chp2;
99#if 0				/* enable this if you want to trace procmail */
100  kill(getpid(),SIGSTOP);/*raise(SIGSTOP);*/
101#endif
102  newid();
103  ;{ int presenviron,override;char*fromwhom=0;
104     const char*idhint=0;gid_t egid=getegid();
105     presenviron=Deliverymode=mailfilter=override=0;
106     Openlog(procmailn,LOG_PID,LOG_MAIL);		  /* for the syslogd */
107     if(argc)			       /* sanity check, any argument at all? */
108      { Deliverymode=!!strncmp(lastdirsep(argv0=argv[0]),procmailn,
109	 STRLEN(procmailn));
110	for(argc=0;(chp2=(char*)argv[++argc])&&*chp2=='-';)
111	   for(;;)				       /* processing options */
112	    { switch(*++chp2)
113	       { case VERSIONOPT:
114		    usage();
115		    return EXIT_SUCCESS;
116		 case HELPOPT1:case HELPOPT2:elog(pmusage);elog(PM_HELP);
117		    elog(PM_QREFERENCE);
118		    return EX_USAGE;
119		 case PRESERVOPT:presenviron=1;
120		    continue;
121		 case MAILFILTOPT:mailfilter=1;
122		    continue;
123		 case OVERRIDEOPT:override=1;
124		    continue;
125		 case BERKELEYOPT:case ALTBERKELEYOPT:berkeley=1;
126		    continue;
127		 case TEMPFAILOPT:retval=EX_TEMPFAIL;
128		    continue;
129		 case FROMWHOPT:case ALTFROMWHOPT:
130		    if(*++chp2)
131		       fromwhom=chp2;
132		    else if(chp2=(char*)argv[argc+1])
133		       argc++,fromwhom=chp2;
134		    else
135		       nlog("Missing name\n");
136		    break;
137		 case ARGUMENTOPT:
138		  { static struct dyna_array newargv;
139		    if(*++chp2)
140		       goto setarg;
141		    else if(chp2=(char*)argv[argc+1])
142		     { argc++;
143setarg:		       app_valp(newargv,(const char*)chp2);
144		       restargv=&(acc_valp(newargv,0));
145		       crestarg++;
146		     }
147		    else
148		       nlog("Missing argument\n");
149		    break;
150		  }
151		 case DELIVEROPT:
152		    if(!*(chp= ++chp2)&&!(chp=(char*)argv[++argc]))
153		     { nlog(misrecpt);
154		       break;
155		     }
156		    else
157		     { Deliverymode=1;
158		       goto last_option;
159		     }
160		 case LMTPOPT:
161#ifdef LMTP
162		    Deliverymode=2;
163		    goto last_option;
164#else
165		    nlog("LMTP support not enabled in this binary\n");
166		    return EX_USAGE;
167#endif
168		 case '-':
169		    if(!*++chp2)
170		     { argc++;
171		       goto last_option;
172		     }
173		 default:nlog("Unrecognised options:");logqnl(chp2);
174		    elog(pmusage);elog("Processing continued\n");
175		 case '\0':;
176	       }
177	      break;
178	    }
179      }
180     if(Deliverymode==1&&!(chp=chp2))
181	nlog(misrecpt),Deliverymode=0;
182last_option:
183     switch(Deliverymode)
184      { case 0:
185	   idhint=getenv(lgname);
186	   if(mailfilter&&crestarg)
187	    { crestarg=0;			     /* -m will supersede -a */
188conflopt:     nlog(conflicting);elog("options\n");elog(pmusage);
189	    }
190	   break;
191#ifdef LMTP
192	case 2:
193	   if(fromwhom)
194	    { fromwhom=0;				  /* -z disables -f, */
195	      goto confldopt;					/* -p and -m */
196	    }
197#endif
198	case 1:
199	   if(presenviron||mailfilter)
200confldopt:  { presenviron=mailfilter=0;		    /* -d disables -p and -m */
201	      goto conflopt;
202	    }
203	   break;
204	default:				       /* this cannot happen */
205	   abort();
206      }
207     cleanupenv(presenviron);
208     ;{ auth_identity*pass,*passinvk;auth_identity*spassinvk;
209	uid_t euid=geteuid();
210	uid=getuid();gid=getgid();
211	spassinvk=auth_newid();passinvk=savepass(spassinvk,uid);   /* are we */
212	checkprivFrom_(euid,passinvk?auth_username(passinvk):0,override);
213	doumask(INIT_UMASK);		   /* allowed to set the From_ line? */
214	while((savstdout=rdup(STDOUT))<=STDERR)
215	 { rclose(savstdout);		       /* move stdout out of the way */
216	   if(0>(savstdout=opena(devnull)))
217	      goto nodevnull;
218	   syslog(LOG_ALERT,"Descriptor %d was not open\n",savstdout);
219	 }
220	fclose(stdout);rclose(STDOUT);			/* just to make sure */
221	if(0>opena(devnull))
222nodevnull:
223	 { writeerr(devnull);syslog(LOG_EMERG,slogstr,errwwriting,devnull);
224	   return EX_OSFILE;			  /* couldn't open /dev/null */
225	 }
226#ifdef console
227	opnlog(console);
228#endif
229	setbuf(stdin,(char*)0);allocbuffers(linebuf,1);
230#ifdef SIGXCPU
231	signal(SIGXCPU,SIG_IGN);signal(SIGXFSZ,SIG_IGN);
232#endif
233#ifdef SIGLOST
234	signal(SIGLOST,SIG_IGN);
235#endif
236#if DEFverbose
237	verboff();verbon();
238#else
239	verbon();verboff();
240#endif
241#ifdef SIGCHLD
242	signal(SIGCHLD,SIG_DFL);
243#endif
244	signal(SIGPIPE,SIG_IGN);
245	setcomsat(empty);			  /* turn on biff by default */
246	ultstr(0,(unsigned long)uid,buf);filled=0;
247	if(!passinvk||!(chp2=(char*)auth_username(passinvk)))
248	   chp2=buf;
249#ifdef LMTP
250	if(Deliverymode==2)
251	 { auth_identity**rcpts,**lastrcpt,**currcpt;
252	   currcpt=rcpts=lmtp(&lastrcpt,chp2);
253	   if(currcpt+1!=lastrcpt)     /* if there's more than one recipient */
254	      lockblock(&themail);     /* then no one can write to the block */
255	   else				     /* otherwise the only recipient */
256	      private(1);				/* gets the original */
257	   while(currcpt<lastrcpt)
258	    { if(!(pidchild=sfork()))
259	       { setupsigs();
260		 pass= *currcpt++;
261		 while(currcpt<lastrcpt)
262		    auth_freeid(*currcpt++);
263		 freeoverread();
264		 free(rcpts);newid();gargv=&nullp;
265		 goto dorcpt;
266	       }
267	      if(forkerr(pidchild,procmailn))
268		 lexitcode=EX_OSERR;
269	      else
270		 waitfor(pidchild);
271	      lmtpresponse(lexitcode);
272	      pidchild=0;
273	      auth_freeid(*currcpt++);
274	    }
275	   free(rcpts);
276	   flushoverread();		 /* pass upwards the extra LMTP data */
277	   exit(EXIT_SUCCESS);
278	 }
279#endif
280	setupsigs();
281	makeFrom(fromwhom,chp2);
282	readmail(0,0L);			      /* read in the mail completely */
283	if(Deliverymode)
284	 { if(argv[argc+1])			 /* more than one recipient? */
285	    { private(0);				    /* time to share */
286	      lockblock(&themail);
287	    }
288	   else
289	      private(1);
290	   do			  /* chp should point to the first recipient */
291	    { chp2=chp;
292	      if(argv[++argc])			  /* more than one recipient */
293		 if(pidchild=sfork())
294		  { if(forkerr(pidchild,procmailn)||
295		       waitfor(pidchild)!=EXIT_SUCCESS)
296		       retvl2=retval;
297		    pidchild=0;		      /* loop for the next recipient */
298		  }
299		 else
300		  { newid();
301		    while(argv[++argc]);    /* skip till end of command line */
302		    break;
303		  }
304	    }
305	   while(chp=(char*)argv[argc]);
306	 }
307	gargv=argv+argc;			 /* save it for nextrcfile() */
308	if(Deliverymode)	/* try recipient without changing case first */
309	 { if(!(pass=auth_finduser(chp2,-1)))	    /* chp2 is the recipient */
310	    { static const char unkuser[]="Unknown user";
311	      nlog(unkuser);logqnl(chp2);syslog(LOG_ERR,slogstr,unkuser,chp2);
312	      return EX_NOUSER;			/* we don't handle strangers */
313	    }
314dorcpt:	   if(enoughprivs(passinvk,euid,egid,auth_whatuid(pass),
315	    auth_whatgid(pass)))
316	      goto Setuser;
317	   nlog(insufprivs);
318	   syslog(LOG_CRIT,"Insufficient privileges to deliver to \"%s\"\n",
319	    chp2);
320	   return EX_NOPERM;	      /* need more mana, decline the request */
321	 }
322	else
323	 { int commandlinerc=nextrcfile();
324	   if(presenviron)		      /* preserving the environment? */
325	      etcrc=0;				    /* don't read etcrc then */
326	   if(commandlinerc)			     /* command-line rcfile? */
327	    { etcrc=0;				 /* forget etcrc and comsat: */
328	      setcomsat(DEFcomsat);			/* the internal flag */
329	      if(!presenviron)			 /* and usually the variable */
330		 setdef(scomsat,DEFcomsat);
331	    }
332	   if(mailfilter)
333	    { if(!commandlinerc)
334	       { nlog("Missing rcfile\n");
335		 return EX_NOINPUT;
336	       }
337#ifdef ETCRCS
338	      ;{ static const char etcrcs[]=ETCRCS;
339		 if(!strncmp(etcrcs,rcfile,STRLEN(etcrcs)))
340		  { struct stat stbuf; /* path starts with /etc/procmailrcs/ */
341		   /*
342		    *	although the filename starts with /etc/procmailrcs/
343		    *	we will now check if it does not contain any backward
344		    *	references which would allow it to escape the secure
345		    *	tree; look for /../ sequences
346		    */
347		    for(chp=(char*)rcfile+STRLEN(etcrcs)-1;
348			chp;		       /* any devious embedded /../? */
349			chp=strpbrk(chp,dirsep))
350		       if(!strncmp(pardir,++chp,STRLEN(pardir))&&
351			  (chp+=STRLEN(pardir),strchr(dirsep,*chp)))
352			  goto nospecial;	  /* yes, security violation */
353#ifdef CAN_chown
354		    *(chp=strcpy(buf2,etcrcs)+STRLEN(etcrcs))=chCURDIR;
355		    *++chp='\0';
356#endif
357		   /*
358		    *	so far, so good, now verify the credentials down to the
359		    *	last bit
360		    */
361		    if(presenviron||			  /* -p is dangerous */
362		       commandlinerc!=2||     /* so are variable assignments */
363#ifdef CAN_chown		  /* anyone can chown in this filesystem so: */
364		       stat(buf2,&stbuf)||	     /* the /etc/procmailrcs */
365		       !S_ISDIR(stbuf.st_mode)||	/* directory must be */
366		       stbuf.st_uid!=ROOT_uid&&		    /* owned by root */
367			chown(buf2,ROOT_uid,stbuf.st_gid)||
368		       stbuf.st_mode&(S_IXGRP|S_IXOTH)&&   /* and accessible */
369			chmod(buf2,S_IRWXU)||		   /* to no one else */
370#endif
371		       lstat(rcfile,&stbuf)||		/* it seems to exist */
372		       !enoughprivs(passinvk,euid,egid,stbuf.st_uid,
373			stbuf.st_gid)||		   /* can we do this at all? */
374		       S_ISDIR(stbuf.st_mode)||		  /* no directories! */
375		       !savepass(spassinvk,stbuf.st_uid)     /* user exists? */
376		      )
377nospecial:	     { static const char densppr[]=
378			"Denying special privileges for";
379		       nlog(densppr);logqnl(rcfile);
380		       syslog(LOG_ALERT,slogstr,densppr,rcfile);
381		     }
382		    else			    /* no security violation */
383		       mailfilter=2,passinvk=spassinvk;
384		  }				      /* accept new identity */
385	       }
386#endif
387	    }
388	 }
389	pass=passinvk;
390	if(passinvk&&idhint)	      /* if same uid as $LOGNAME, use latter */
391	 { auth_identity*idpass=auth_finduser((char*)idhint,0);
392	   if(idpass)
393	    { if(auth_whatuid(passinvk)==auth_whatuid(idpass))
394		 pass=idpass;
395	    }
396	 }
397	if(pass)	      /* set preferred uid to the intended recipient */
398Setuser: { gid=auth_whatgid(pass);uid=auth_whatuid(pass);
399	   if(euid==ROOT_uid&&(chp=(char*)auth_username(pass))&&*chp)
400	      initgroups(chp,gid);
401	   endgrent();
402	 }
403	else					  /* user could not be found */
404	   setids();   /* to prevent security holes, drop any privileges now */
405	initdefenv(pass,buf,!presenviron||!mailfilter);		 /* override */
406	endpwent();auth_freeid(spassinvk);	   /* environment by default */
407      }
408     if(buildpath(orgmail,fdefault,(char*)0))	/* setup DEFAULT and ORGMAIL */
409      { fdefault=empty;			   /* uh, Houston, we have a problem */
410	goto nix_sysmbox;
411      }
412     /*
413      * Processing point of proposed /etc/procmail.conf file
414      */
415     fdefault=tstrdup(buf);sgid=egid;
416     chp=(char*)tgetenv(orgmail);
417     if(mailfilter||!screenmailbox(chp,egid,Deliverymode))
418nix_sysmbox:
419      { sputenv(orgmail);		 /* nix delivering to system mailbox */
420	if(privileged)			       /* don't need this any longer */
421	   privileged=priv_DONTNEED;
422	if(!strcmp(chp,fdefault))			/* DEFAULT the same? */
423	   free((char*)fdefault),fdefault=empty;		 /* so panic */
424      }						/* bad news, be conservative */
425   }
426  doumask(INIT_UMASK);
427  while(chp=(char*)argv[argc])	       /* interpret command line specs first */
428   { argc++;
429     if(!asenvcpy(chp)&&mailfilter)
430      { gargv= &nullp;				 /* stop at the first rcfile */
431	for(restargv=argv+argc;restargv[crestarg];crestarg++);
432	break;
433      }
434   }
435  if(etcrc)		  /* do we start with an /etc/procmailrc file first? */
436   { if(0<=bopen(etcrc))
437      { yell(drcfile,etcrc);
438#if !DEFverbose
439	if(privileged)
440	   verbose=0;			    /* no peeking in /etc/procmailrc */
441#endif
442	eputenv(defspath,buf);			      /* use the secure PATH */
443	if(mainloop()==rcs_DELIVERED)			   /* run the rcfile */
444	   goto mailed;
445	eputenv(defpath,buf);		 /* switch back to the insecure PATH */
446      }
447   }
448  erestrict=mailfilter!=2;		      /* possibly restrict execs now */
449  if(rcfile&&!mailfilter)			     /* "procmail rcfile..." */
450   { int rctype,dowarning;	 /* only warn about the first missing rcfile */
451     for(dowarning=1;;)
452      { rctype=rct_ABSOLUTE;
453	if(strchr(dirsep,*rcfile))				/* absolute? */
454	   strcpy(buf,rcfile);
455	else if(*rcfile==chCURDIR&&strchr(dirsep,rcfile[1]))   /* ./ prefix? */
456	   strcpy(buf,rcfile),rctype=rct_CURRENT;
457	else			     /* prepend default procmailrc directory */
458	   if(buildpath(maildir,defmaildir,rcfile))
459	      break;
460	if(tryopen(0,rctype,dowarning))
461	 { register int rcs=mainloop();				   /* run it */
462	   if(rcs==rcs_DELIVERED)
463	      goto mailed;					 /* success! */
464	   if(rcs==rcs_EOF)
465	      break;				     /* normal end of rcfile */
466	   if(!nextrcfile())				       /* none left? */
467	      goto mailed;					 /* then out */
468	 }
469	else				      /* not available? try the next */
470	 { dowarning=0;				/* suppress further messages */
471	   if(!nextrcfile())				       /* none left? */
472	      break;						 /* then out */
473	 }
474      }
475   }
476  else
477   { int rctype;
478     if(!rcfile)			    /* no rcfile on the command line */
479      { rctype=rct_DEFAULT;
480	if(buildpath("default rcfile",pmrc,(char*)0))
481	   goto nomore_rc;
482      }
483     else						  /* mailfilter mode */
484      { rctype=strchr(dirsep,*rcfile)?rct_ABSOLUTE:rct_CURRENT;
485	strcpy(buf,rcfile);
486      }
487     if(tryopen(mailfilter==2,rctype,DEFverbose||mailfilter))
488	if(mainloop()!=rcs_EOF)
489	   goto mailed;
490   }
491nomore_rc:
492  if(ifstack.vals)
493     free(ifstack.vals);
494  ;{ int succeed;
495     concon('\n');succeed=0;
496     if(*(chp=(char*)fdefault))				     /* DEFAULT set? */
497      { int len;
498	setuid(uid);			   /* make sure we have enough space */
499	if(linebuf<(len=strlen(chp)+strlen(lockext)+UNIQnamelen))
500	   allocbuffers(linebuf=len,1);	   /* to perform the lock & delivery */
501	if(writefolder(chp,(char*)0,themail.p,filled,0,1))	  /* default */
502	   succeed=1;
503      }						       /* if all else failed */
504     if(!succeed&&*(chp2=(char*)tgetenv(orgmail))&&strcmp(chp2,chp))
505      { rawnonl=0;
506	if(writefolder(chp2,(char*)0,themail.p,filled,0,0))   /* don't panic */
507	   succeed=1;				      /* try the last resort */
508      }
509     if(succeed)				     /* should we panic now? */
510mailed: retval=EXIT_SUCCESS;		  /* we're home free, mail delivered */
511   }
512  unlock(&loclock);Terminate();
513}
514
515static void usage P((void))
516{ elog(procmailn);elog(Version);
517  elog("\nLocking strategies:\tdotlocking");
518#ifndef NOfcntl_lock
519  elog(", fcntl()");				    /* a peek under the hood */
520#endif
521#ifdef USElockf
522  elog(", lockf()");
523#endif
524#ifdef USEflock
525  elog(", flock()");
526#endif
527  elog("\nDefault rcfile:\t\t");elog(pmrc);
528#ifdef GROUP_PER_USER
529  elog("\n\tIt may be writable by your primary group");
530#endif
531  elog("\nYour system mailbox:\t");
532  elog(auth_mailboxname(auth_finduid(getuid(),0)));
533  elog(newline);
534#ifdef USE_MMAP
535  elog("\nLarge messages will be memory mapped during processing.\n");
536#endif
537}
538
539/*
540 *	if we happen to be still running as root, and the rcfile
541 *	is mounted on a secure NFS-partition, we might not be able
542 *	to access it, so check if we can stat it or don't need any
543 *	sgid privileges, if yes, drop all privs and set uid to
544 *	the recipient beforehand
545 */
546static int tryopen(delay_setid,rctype,dowarning)
547 const int delay_setid,rctype,dowarning;
548{ struct stat stbuf;
549  if(!delay_setid&&privileged&&	  /* if we can setid now and we haven't yet, */
550      (privileged==priv_DONTNEED||!stat(buf,&stbuf))) /* and we either don't */
551     setids();	   /* need the privileges or it's accessible, then setid now */
552  if(0>bopen(buf))				   /* try opening the rcfile */
553   { if(dowarning)
554rerr:	readerr(buf);
555     return 0;
556   }
557  if(!delay_setid&&privileged)		   /* if we're not supposed to delay */
558   { closerc();		       /* and we haven't changed yet, then close it, */
559     setids();				 /* transmogrify to prevent peeking, */
560     if(0>bopen(buf))					    /* and try again */
561	goto rerr;		   /* they couldn't read it, so it was bogus */
562   }
563#ifndef NOfstat
564  if(fstat(rc,&stbuf))					    /* the right way */
565#else
566  if(stat(buf,&stbuf))					  /* the best we can */
567#endif
568   { static const char susprcf[]="Suspicious rcfile";
569suspicious_rc:
570     closerc();nlog(susprcf);logqnl(buf);
571     syslog(LOG_ALERT,slogstr,susprcf,buf);
572     goto rerr;
573   }
574  if(delay_setid)			 /* change now if we haven't already */
575     setids();
576  if(rctype==rct_CURRENT)	  /* opened rcfile in the current directory? */
577   { if(!didchd)
578	setmaildir(curdir);
579   }
580  else
581    /*
582     * OK, so now we have opened an absolute rcfile, but for security
583     * reasons we only accept it if it is owned by the recipient or
584     * root and is not world writable, and the directory it is in is
585     * not world writable or has the sticky bit set.  If this is the
586     * default rcfile then we also outlaw group writability.
587     */
588   { register char*chp=lastdirsep(buf),c;
589     c= *chp;
590     if(((stbuf.st_uid!=uid&&stbuf.st_uid!=ROOT_uid||	       /* check uid, */
591	  (stbuf.st_mode&S_IWOTH)||		      /* writable by others, */
592	  rctype==rct_DEFAULT&&		   /* if the default then also check */
593	   (stbuf.st_mode&S_IWGRP)&&		   /* for writable by group, */
594	   (NO_CHECK_stgid||stbuf.st_gid!=gid)
595	 )&&strcmp(devnull,buf)||	     /* /dev/null is a special case, */
596	(*chp='\0',stat(buf,&stbuf))||		     /* check the directory, */
597#ifndef CAN_chown				   /* sticky and can't chown */
598	!(stbuf.st_mode&S_ISVTX)&&		   /* means we don't care if */
599#endif					     /* it's group or world writable */
600	((stbuf.st_mode&(S_IWOTH|S_IXOTH))==(S_IWOTH|S_IXOTH)||
601	 rctype==rct_DEFAULT&&
602	  (stbuf.st_mode&(S_IWGRP|S_IXGRP))==(S_IWGRP|S_IXGRP)&&
603	  (NO_CHECK_stgid||stbuf.st_gid!=gid))))
604      { *chp=c;
605	goto suspicious_rc;
606      }
607     *chp=c;
608   }
609  yell(drcfile,buf);
610  /*
611   *	Chdir now if we haven't already
612   */
613  if(!didchd)				       /* have we done this already? */
614   { const char*chp;
615     if(buildpath(maildir,defmaildir,(char*)0))
616	exit(EX_OSERR);		   /* something was wrong: give up the ghost */
617     if(chdir(chp=buf))
618      { chderr(buf);		      /* no, well, then try an initial chdir */
619	chp=tgetenv(home);
620	if(!strcmp(chp,buf)||chdir(chp))
621	   chderr(chp),chp=curdir;		/* that didn't work, use "." */
622      }
623     setmaildir(chp);
624   }
625  return 1;						 /* we're good to go */
626}
627
628static int mainloop P((void))
629{ int lastsucc,lastcond,prevcond,i,skiprc;register char*chp,*tolock;
630  lastsucc=lastcond=prevcond=skiprc=0;
631  tolock=0;
632  do
633   { unlock(&loclock);				/* unlock any local lockfile */
634     goto commint;
635     do
636      { skipline();
637commint:do skipspace();					  /* skip whitespace */
638	while(testB('\n'));
639      }
640     while(testB('#'));					   /* no comment :-) */
641     if(testB(':'))				       /* check for a recipe */
642      { int locknext,succeed;char*startchar;long tobesent;
643	static char flags[maxindex(exflags)];
644	do
645	 { int nrcond;
646	   if(readparse(buf,getb,0,skiprc))
647	      return rcs_EOF;			      /* give up on this one */
648	   ;{ char*temp;			 /* so that chp isn't forced */
649	      nrcond=strtol(buf,&temp,10);chp=temp;	      /* into memory */
650	    }
651	   if(chp==buf)					 /* no number parsed */
652	      nrcond= -1;
653	   if(tolock)		 /* clear temporary buffer for lockfile name */
654	      free(tolock);
655	   for(i=maxindex(flags);i;i--)			  /* clear the flags */
656	      flags[i]=0;
657	   for(tolock=0,locknext=0;;)
658	    { chp=skpspace(chp);
659	      switch(i= *chp++)
660	       { default:
661		    ;{ char*flg;
662		       if(!(flg=strchr(exflags,i)))	    /* a valid flag? */
663			{ chp--;
664			  break;
665			}
666		       flags[flg-exflags]=1;		     /* set the flag */
667		     }
668		 case '\0':
669		    if(chp!=Tmnate)		/* if not the real end, skip */
670		       continue;
671		    break;
672		 case ':':locknext=1;	    /* yep, local lockfile specified */
673		    if(*chp||++chp!=Tmnate)
674		       tolock=tstrdup(chp),chp=strchr(chp,'\0')+1;
675	       }
676	      concatenate(chp);skipped(chp);		/* display leftovers */
677	      break;
678	    }				      /* parse & test the conditions */
679	   i=conditions(flags,prevcond,lastsucc,lastcond,skiprc!=0,nrcond);
680	   if(!skiprc)
681	    { if(!flags[ALSO_NEXT_RECIPE]&&!flags[ALSO_N_IF_SUCC])
682		 lastcond=i==1;		   /* save the outcome for posterity */
683	      if(!prevcond||!flags[ELSE_DO])
684		 prevcond=i==1;	      /* ditto for `else if' like constructs */
685	    }
686	 }
687	while(i==2);			     /* missing in action, reiterate */
688	startchar=themail.p;tobesent=filled;
689	if(flags[PASS_HEAD])			    /* body, header or both? */
690	 { if(!flags[PASS_BODY])
691	      tobesent=thebody-themail.p;
692	 }
693	else if(flags[PASS_BODY])
694	   tobesent-=(startchar=thebody)-themail.p;
695	Stdout=0;succeed=sh=0;
696	pwait=flags[WAIT_EXIT]|flags[WAIT_EXIT_QUIET]<<1;
697	ignwerr=flags[IGNORE_WRITERR];skipspace();
698	if(i)
699	   zombiecollect(),concon('\n');
700progrm: if(testB('!'))					 /* forward the mail */
701	 { if(!i)
702	      skiprc|=1;
703	   if(strlcpy(buf,sendmail,linebuf)>=linebuf)
704	      goto fail;
705	   chp=strchr(buf,'\0');
706	   if(*flagsendmail)
707	    { char*q;int got=0;
708	      if(!(q=simplesplit(chp+1,flagsendmail,buf+linebuf-1,&got)))
709		 goto fail;
710	      *(chp=q)='\0';
711	    }
712	   if(readparse(chp+1,getb,0,skiprc))
713	      goto fail;
714	   if(i)
715	    { if(startchar==themail.p)
716	       { startchar[filled]='\0';		     /* just in case */
717		 startchar=(char*)skipFrom_(startchar,&tobesent);
718	       }      /* leave off leading From_ -- it confuses some mailers */
719	      goto forward;
720	    }
721	   skiprc&=~1;
722	 }
723	else if(testB('|'))				    /* pipe the mail */
724	 { chp=buf2;
725	   if(getlline(chp,buf2+linebuf))	 /* get the command to start */
726	      goto commint;
727	   if(i)
728	    { metaparse(buf2);
729	      if(!sh&&buf+1==Tmnate)		      /* just a pipe symbol? */
730	       { *buf='|';*(char*)(Tmnate++)='\0';		  /* fake it */
731		 goto tostdout;
732	       }
733forward:      if(locknext)
734	       { if(!tolock)	   /* an explicit lockfile specified already */
735		  { *buf2='\0';	    /* find the implicit lockfile ('>>name') */
736		    for(chp=buf;i= *chp++;)
737		       if(i=='>'&&*chp=='>')
738			{ chp=skpspace(chp+1);
739			  tmemmove(buf2,chp,i=strcspn(chp,EOFName));
740			  buf2[i]='\0';
741			  if(sh)	 /* expand any environment variables */
742			   { chp=tstrdup(buf);sgetcp=buf2;
743			     if(readparse(buf,sgetc,0,0))
744			      { *buf2='\0';
745				goto nolock;
746			      }
747			     strcpy(buf2,buf);strcpy(buf,chp);free(chp);
748			   }
749			  break;
750			}
751		    if(!*buf2)
752nolock:		     { nlog("Couldn't determine implicit lockfile from");
753		       logqnl(buf);
754		     }
755		  }
756		 lcllock(tolock,buf2);
757		 if(!pwait)		/* try and protect the user from his */
758		    pwait=2;			   /* blissful ignorance :-) */
759	       }
760	      rawnonl=flags[RAW_NONL];
761	      if(flags[CONTINUE]&&(flags[FILTER]||Stdout))
762		 nlog(extrns),elog("copy-flag"),elog(ignrd);
763	      inittmout(buf);
764	      if(flags[FILTER])
765	       { if(startchar==themail.p&&tobesent!=filled)   /* if only 'h' */
766		  { if(!pipthrough(buf,startchar,tobesent))
767		       readmail(1,tobesent),succeed=!pipw;
768		  }
769		 else if(!pipthrough(buf,startchar,tobesent))
770		  { filled=startchar-themail.p;
771		    readmail(0,tobesent);
772		    succeed=!pipw;
773		  }
774	       }
775	      else if(Stdout)			  /* capturing stdout again? */
776		 succeed=!pipthrough(buf,startchar,tobesent);
777	      else if(!pipin(buf,startchar,tobesent,1))	  /* regular program */
778	       { succeed=1;
779		 if(flags[CONTINUE])
780		    goto logsetlsucc;
781		 else
782		    goto frmailed;
783	       }
784	      goto setlsucc;
785	    }
786	 }
787	else if(testB(EOF))
788	   nlog("Incomplete recipe\n");
789	else		   /* dump the mail into a mailbox file or directory */
790	 { int ofiltflag;char*end=buf+linebuf-4;	/* reserve some room */
791	   if(ofiltflag=flags[FILTER])
792	      flags[FILTER]=0,nlog(extrns),elog("filter-flag"),elog(ignrd);
793	   if(chp=gobenv(buf,end))	   /* can it be an environment name? */
794	    { if(chp==end)
795	       { getlline(buf,buf+linebuf);
796		 goto fail;
797	       }
798	      if(skipspace())
799		 chp++;			   /* keep pace with argument breaks */
800	      if(testB('='))		      /* is it really an assignment? */
801	       { int c;
802		 *chp++='=';*chp='\0';
803		 if(skipspace())
804		    chp++;
805		 ungetb(c=getb());
806		 switch(c)
807		  { case '!':case '|':			  /* ok, it's a pipe */
808		       if(i)
809			  Stdout = tstrdup(buf);
810		       goto progrm;
811		  }
812	       }
813	    }			 /* find the end, start of a nesting recipe? */
814	   else if((chp=strchr(buf,'\0'))==buf&&
815		   testB('{')&&
816		   (*chp++='{',*chp='\0',testB(' ')||		      /* } } */
817		    testB('\t')||
818		    testB('\n')))
819	    { if(locknext&&!flags[CONTINUE])
820		 nlog(extrns),elog("locallockfile"),elog(ignrd);
821	      if(flags[PASS_BODY])
822		 nlog(extrns),elog("deliver-body flag"),elog(ignrd);
823	      if(flags[PASS_HEAD])
824		 nlog(extrns),elog("deliver-head flag"),elog(ignrd);
825	      if(flags[IGNORE_WRITERR])
826		 nlog(extrns),elog("ignore-write-error flag"),elog(ignrd);
827	      if(flags[RAW_NONL])
828		 nlog(extrns),elog("raw-mode flag"),elog(ignrd);
829	      if(!i)						/* no match? */
830		 skiprc+=2;		      /* increase the skipping level */
831	      else
832	       { app_vali(ifstack,prevcond);		    /* push prevcond */
833		 app_vali(ifstack,lastcond);		    /* push lastcond */
834		 if(locknext)
835		  { lcllock(tolock,"");
836		    if(!pwait)		/* try and protect the user from his */
837		       pwait=2;			   /* blissful ignorance :-) */
838		  }
839		 succeed=1;
840		 if(flags[CONTINUE])
841		  { yell("Forking",procmailn);
842		    private(0);			      /* can't share anymore */
843		    inittmout(procmailn);onguard();
844		    if(!(pidchild=sfork()))		   /* clone yourself */
845		     { if(loclock)	      /* lockfiles are not inherited */
846			  free(loclock),loclock=0;
847		       if(globlock)
848			  free(globlock),globlock=0;	     /* clear up the */
849		       newid();offguard();duprcs();	  /* identity crisis */
850		     }
851		    else
852		     { offguard();
853		       if(forkerr(pidchild,procmailn))
854			  succeed=0;	       /* tsk, tsk, no cloning today */
855		       else
856			{ int excode;	  /* wait for our significant other? */
857			  if(pwait&&
858			     (excode=waitfor(pidchild))!=EXIT_SUCCESS)
859			   { if(!(pwait&2)||verbose)	 /* do we report it? */
860				progerr(procmailn,excode,pwait&2);
861			     succeed=0;
862			   }
863			  pidchild=0;skiprc+=2;	     /* skip over the braces */
864			  ifstack.filled-=2;		/* retract the stack */
865			}
866		     }
867		  }
868		 goto setlsucc;			/* definitely no logabstract */
869	       }
870	      continue;
871	    }
872	   if(!i)						/* no match? */
873	      skiprc|=1;		  /* temporarily disable subprograms */
874	   if(readparse(chp,getb,0,skiprc))
875fail:	    { succeed=0;
876	      goto setlsucc;
877	    }
878	   if(i)
879	    { if(ofiltflag)	       /* protect who use bogus filter-flags */
880		 startchar=themail.p,tobesent=filled;	    /* whole message */
881tostdout:     rawnonl=flags[RAW_NONL];
882	      if(locknext)		     /* write to a file or directory */
883		 lcllock(tolock,buf);
884	      inittmout(buf);		  /* to break messed-up kernel locks */
885	      if(writefolder(buf,strchr(buf,'\0')+1,startchar,tobesent,
886		  ignwerr,0)&&
887		 (succeed=1,!flags[CONTINUE]))
888frmailed:      { if(ifstack.vals)
889		    free(ifstack.vals);
890		 return rcs_DELIVERED;
891	       }
892logsetlsucc:  if(succeed&&flags[CONTINUE]&&lgabstract==2)
893		 logabstract(tgetenv(lastfolder));
894setlsucc:     rawnonl=0;lastsucc=succeed;lasttell= -1;	       /* for comsat */
895	      resettmout();			  /* clear any pending timer */
896	    }
897	   skiprc&=~1;				     /* reenable subprograms */
898	 }
899      } /* { */
900     else if(testB('}'))					/* end block */
901      { if(skiprc>1)					    /* just skipping */
902	   skiprc-=2;					   /* decrease level */
903	else if(ifstack.filled>ifdepth)	      /* restore lastcond from stack */
904	 { lastcond=acc_vali(ifstack,--ifstack.filled);
905	   prevcond=acc_vali(ifstack,--ifstack.filled);		 /* prevcond */
906	 }							  /* as well */
907	else
908	   nlog("Closing brace unexpected\n");		      /* stack empty */
909      }
910     else				    /* then it must be an assignment */
911      { char*end=buf+linebuf;
912	if(!(chp=gobenv(buf,end)))
913	 { if(!*buf)					/* skip a word first */
914	      getbl(buf,end);				      /* then a line */
915	   skipped(buf);				/* display leftovers */
916	   continue;
917	 }
918	if(chp==end)				      /* overflow => give up */
919	   break;
920	skipspace();
921	if(testB('='))				   /* removal or assignment? */
922	 { *chp='=';
923	   if(readparse(++chp,getb,1,skiprc))
924	      continue;
925	 }
926	else
927	   *++chp='\0';			     /* throw in a second terminator */
928	if(!skiprc)
929	 { const char*p;
930	   p=sputenv(buf);
931	   chp[-1]='\0';
932	   asenv(p);
933	 }
934      }
935     if(rc<0)				   /* abnormal exit from the rcfile? */
936	return rcs_HOST;
937   }
938  while(!testB(EOF)||(skiprc=0,poprc()));
939  return rcs_EOF;
940}
941