/************************************************************************
 *	procmail - The autonomous mail processor			*
 *									*
 *	It has been designed to be able to be run suid root and (in	*
 *	case your mail spool area is *not* world writable) sgid		*
 *	mail (or daemon), without creating security holes.		*
 *									*
 *	Seems to be perfect.						*
 *									*
 *	Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands	*
 *	Copyright (c) 1999-2000, Philip Guenther, The United States	*
 *							of America	*
 *	#include "../README"						*
 ************************************************************************/
#ifdef RCS
static /*const*/char rcsid[]=
 "$Id: procmail.c,v 1.152.2.8 2000/08/22 04:02:16 guenther Exp $";
#endif
#include "../patchlevel.h"
#include "procmail.h"
#include "acommon.h"
#include "sublib.h"
#include "robust.h"
#include "shell.h"
#include "misc.h"
#include "pipes.h"
#include "common.h"
#include "cstdio.h"
#include "exopen.h"
#include "regexp.h"
#include "mcommon.h"
#include "goodies.h"
#include "locking.h"
#include "mailfold.h"
#include "lastdirsep.h"
#include "authenticate.h"
#include "foldinfo.h"

static const char*const nullp,From_[]=FROM,exflags[]=RECFLAGS,
 drcfile[]="Rcfile:",pmusage[]=PM_USAGE,*etcrc=ETCRC,
 misrecpt[]="Missing recipient\n",extrns[]="Extraneous ",ignrd[]=" ignored\n",
 pardir[]=chPARDIR,curdir[]={chCURDIR,'\0'},defspath[]=DEFSPATH,
 defpath[]=DEFPATH,attemptst[]="Attempt to fake stamp by";
char*buf,*buf2,*loclock,*tolock;
const char shell[]="SHELL",lockfile[]="LOCKFILE",newline[]="\n",binsh[]=BinSh,
 unexpeof[]="Unexpected EOL\n",*const*gargv,*const*restargv= &nullp,*sgetcp,
 pmrc[]=PROCMAILRC,*rcfile=pmrc,dirsep[]=DIRSEP,devnull[]=DevNull,empty[]="",
 lgname[]="LOGNAME",executing[]="Executing",oquote[]=" \"",cquote[]="\"\n",
 procmailn[]="procmail",whilstwfor[]=" whilst waiting for ",home[]="HOME",
 host[]="HOST",*defdeflock=empty,*argv0=empty,pathtoolong[]=" path too long",
 slogstr[]="%s \"%s\"",conflicting[]="Conflicting ",orgmail[]="ORGMAIL",
 insufprivs[]="Insufficient privileges\n",
 exceededlb[]="Exceeded LINEBUF\n",errwwriting[]="Error while writing to";
char*Stdout;
int retval=EX_CANTCREAT,retvl2=EXIT_SUCCESS,sh,pwait,lcking,rcstate,rc= -1,
 ignwerr,lexitcode=EXIT_SUCCESS,asgnlastf,crestarg,skiprc,savstdout,berkeley,
 mailfilter,erestrict,ifdepth;			       /* depth of outermost */
struct dyna_long ifstack;	      /* brace in this rcfile, and the stack */
size_t linebuf=mx(DEFlinebuf,1024/*STRLEN(systm_mbox)<<1*/);
volatile int nextexit;			       /* if termination is imminent */
pid_t thepid;
long filled,lastscore;	       /* the length of the mail, and the last score */
char*themail,*thebody;			    /* the head and body of the mail */
uid_t uid;
gid_t gid,sgid;

static auth_identity*savepass(spass,uid)auth_identity*const spass;
 const uid_t uid;
{ const auth_identity*tpass;
  if(auth_filledid(spass)&&auth_whatuid(spass)==uid)
     goto ret;
  if(tpass=auth_finduid(uid,0))				  /* save by copying */
   { auth_copyid(spass,tpass);
ret: return spass;
   }
  return (auth_identity*)0;
}

#if 0
#define wipetcrc()	(etcrc&&(etcrc=0,closerc(),eputenv(defpath,buf),1))
#else
static int wipetcrc P((void))	  /* stupid function to avoid a compiler bug */
{ if(etcrc)
   { etcrc=0;closerc();
     eputenv(defpath,buf);
     return 1;
   }
  return 0;
}
#endif

int main(argc,argv)const char*const argv[];
{ register char*chp,*chp2;register int i;int suppmunreadable;
#if 0				/* enable this if you want to trace procmail */
  kill(getpid(),SIGSTOP);/*raise(SIGSTOP);*/
#endif
  newid();
  ;{ int presenviron,Deliverymode,override;char*fromwhom=0;
     const char*idhint=0;gid_t egid=getegid();
     Deliverymode=mailfilter=override=0;
     Openlog(procmailn,LOG_PID,LOG_MAIL);		  /* for the syslogd */
     if(argc)			       /* sanity check, any argument at all? */
      { Deliverymode=strncmp(lastdirsep(argv0=argv[0]),procmailn,
	 STRLEN(procmailn));
	for(presenviron=argc=0;(chp2=(char*)argv[++argc])&&*chp2=='-';)
	   for(;;)				       /* processing options */
	    { switch(*++chp2)
	       { case VERSIONOPT:elog(procmailn);elog(VERSION);
		    elog("\nLocking strategies:\tdotlocking");
#ifndef NOfcntl_lock
		    elog(", fcntl()");		    /* a peek under the hood */
#endif
#ifdef USElockf
		    elog(", lockf()");
#endif
#ifdef USEflock
		    elog(", flock()");
#endif
		    elog("\nDefault rcfile:\t\t");elog(pmrc);
#ifdef GROUP_PER_USER
		    elog("\n\tIt may be writable by your primary group");
#endif
		    elog("\nYour system mailbox:\t");
		    elog(auth_mailboxname(auth_finduid(getuid(),0)));
		    elog(newline);
		    return EXIT_SUCCESS;
		 case HELPOPT1:case HELPOPT2:elog(pmusage);elog(PM_HELP);
		    elog(PM_QREFERENCE);
		    return EX_USAGE;
		 case PRESERVOPT:presenviron=1;
		    continue;
		 case MAILFILTOPT:mailfilter=1;
		    continue;
		 case OVERRIDEOPT:override=1;
		    continue;
		 case BERKELEYOPT:case ALTBERKELEYOPT:berkeley=1;
		    continue;
		 case TEMPFAILOPT:retval=EX_TEMPFAIL;
		    continue;
		 case FROMWHOPT:case ALTFROMWHOPT:
		    if(*++chp2)
		       fromwhom=chp2;
		    else if(chp2=(char*)argv[argc+1])
		       argc++,fromwhom=chp2;
		    else
		       nlog("Missing name\n");
		    break;
		 case ARGUMENTOPT:
		  { static const char*argv1[]={empty,0};
		    if(*++chp2)
		       goto setarg;
		    else if(chp2=(char*)argv[argc+1])
		     { argc++;
setarg:		       *argv1=chp2;restargv=argv1;crestarg=1;
		     }
		    else
		       nlog("Missing argument\n");
		    break;
		  }
		 case DELIVEROPT:
		    if(!*(chp= ++chp2)&&!(chp=(char*)argv[++argc]))
		     { nlog(misrecpt);
		       break;
		     }
		    else
		     { Deliverymode=1;
		       goto last_option;
		     }
		 case '-':
		    if(!*chp2)
		     { argc++;
		       goto last_option;
		     }
		 default:nlog("Unrecognised options:");logqnl(chp2);
		    elog(pmusage);elog("Processing continued\n");
		 case '\0':;
	       }
	      break;
	    }
      }
     if(Deliverymode&&!(chp=chp2))
	nlog(misrecpt),Deliverymode=0;
last_option:
     if(Deliverymode&&presenviron)
      { presenviron=0;					   /* -d disables -p */
	goto conflopt;
      }
     if(mailfilter)
      { if(Deliverymode)				 /* -d supersedes -m */
	 { mailfilter=0;
	   goto conflopt;
	 }
	if(crestarg)				     /* -m will supersede -a */
conflopt:  nlog(conflicting),elog("options\n"),elog(pmusage);
      }
     if(!Deliverymode)
	idhint=getenv(lgname);
     if(!presenviron)				     /* drop the environment */
      { const char**emax=(const char**)environ,**ep,*const*kp;
	static const char*const keepenv[]=KEEPENV;
	for(kp=keepenv;*kp;kp++)		     /* preserve a happy few */
	   for(i=strlen(*kp),ep=emax;chp2=(char*)*ep;ep++)
	      if(!strncmp(*kp,chp2,(size_t)i)&&(chp2[i]=='='||chp2[i-1]=='_'))
	       { *ep= *emax;*emax++=chp2;			 /* swap 'em */
		 break;
	       }
	*emax=0;					    /* drop the rest */
      }
#ifdef LD_ENV_FIX
     ;{ const char**emax=(const char**)environ,**ep;
	static const char*ld_[]=
	 {"LD_","_RLD","LIBPATH=","ELF_LD_","AOUT_LD_",0};
	for(ep=emax;*emax;emax++);	  /* find the end of the environment */
	for(;*ep;ep++)
	 { const char**ldp,*p;
	   for(ldp=ld_;p= *ldp++;)
	      if(!strncmp(*ep,p,strlen(p)))	/* if it starts with LD_ (or */
	       { *ep--= *--emax;*emax=0;       /* similar) copy from the end */
		 break;
	       }
	 }
      }
#endif /* LD_ENV_FIX */
     ;{ auth_identity*pass,*passinvk;auth_identity*spassinvk;int privs;
	uid_t euid=geteuid();
	spassinvk=auth_newid();passinvk=savepass(spassinvk,uid=getuid());
	privs=1;gid=getgid();
	;{ static const char*const trusted_ids[]=TRUSTED_IDS;
	   if(Deliverymode&&*trusted_ids&&uid!=euid)
	    { struct group*grp;const char*const*kp;
	      if(passinvk)		      /* check out the invoker's uid */
		 for(chp2=(char*)auth_username(passinvk),kp=trusted_ids;*kp;)
		    if(!strcmp(chp2,*kp++)) /* is it amongst the privileged? */
		       goto privileged;
	      if(grp=getgrgid(gid))	      /* check out the invoker's gid */
		 for(chp2=grp->gr_name,kp=trusted_ids;*kp;)
		    if(!strcmp(chp2,*kp++))   /* is it among the privileged? */
		       goto privileged;
	      privs=0;
	    }
	 }
privileged:				       /* move stdout out of the way */
	endgrent();doumask(INIT_UMASK);
	while((savstdout=rdup(STDOUT))<=STDERR)
	 { rclose(savstdout);
	   if(0>(savstdout=opena(devnull)))
	      goto nodevnull;
	   syslog(LOG_ALERT,"Descriptor %d was not open\n",savstdout);
	 }
	fclose(stdout);rclose(STDOUT);			/* just to make sure */
	if(0>opena(devnull))
nodevnull:
	 { writeerr(devnull);syslog(LOG_EMERG,slogstr,errwwriting,devnull);
	   return EX_OSFILE;			     /* couldn't open stdout */
	 }
#ifdef console
	opnlog(console);
#endif
	setbuf(stdin,(char*)0);mallocbuffers(linebuf,1);
#ifdef SIGXCPU
	signal(SIGXCPU,SIG_IGN);signal(SIGXFSZ,SIG_IGN);
#endif
#ifdef SIGLOST
	signal(SIGLOST,SIG_IGN);
#endif
#if DEFverbose
	verboff();verbon();
#else
	verbon();verboff();
#endif
#ifdef SIGCHLD
	signal(SIGCHLD,SIG_DFL);
#endif
	signal(SIGPIPE,SIG_IGN);qsignal(SIGTERM,srequeue);
	qsignal(SIGINT,sbounce);qsignal(SIGHUP,sbounce);
	qsignal(SIGQUIT,slose);signal(SIGALRM,(void(*)())ftimeout);
	ultstr(0,(unsigned long)uid,buf);filled=0;
	if(!passinvk||!(chp2=(char*)auth_username(passinvk)))
	   chp2=buf;
	;{ const char*fwhom;size_t lfr,linv;int tstamp;
	   tstamp=fromwhom&&*fromwhom==REFRESH_TIME&&!fromwhom[1];fwhom=chp2;
	   if(fromwhom&&!tstamp)
	    { if(!privs&&!strcmp(fromwhom,fwhom))
		 privs=1; /* if -f user is the same as the invoker, allow it */
	      if(!privs&&fromwhom&&override)
	       { if(verbose)
		    nlog(insufprivs);		      /* ignore the bogus -f */
		 syslog(LOG_ERR,slogstr,attemptst,fwhom);fromwhom=0;
	       }
	      else
		 fwhom=fromwhom;
	    }
	   thebody=themail=
	    malloc(2*linebuf+(lfr=strlen(fwhom))+(linv=strlen(chp2)));
	   if(Deliverymode||fromwhom)  /* need to peek for a leading From_ ? */
	    { char*rstart;int r;static const char Fakefield[]=FAKE_FIELD;
	      ;{ time_t t;				 /* the current time */
		 t=time((time_t*)0);strcat(strcpy(buf2,"  "),ctime(&t));
	       }
	      lfr+=STRLEN(From_)+(r=strlen(buf2));
	      if(tstamp)
		 tstamp=r;			   /* save time stamp length */
	      if(privs)					 /* privileged user? */
		 linv=0;		 /* yes, so no need to insert >From_ */
	      else
		 linv+=STRLEN(Fakefield)+r;
	      while(1==(r=rread(STDIN,themail,1))&&*themail=='\n');
	      i=0;rstart=themail;			     /* skip garbage */
	      if(r>0&&STRLEN(From_)<=(i=rread(	      /* is it a From_ line? */
	       STDIN,rstart+1,(int)(linebuf-2-1))+1)&&eqFrom_(themail))
	       { rstart[i]='\0';
		 if(!(rstart=strchr(rstart,'\n')))
		  { do				     /* drop long From_ line */
		     { if((i=rread(STDIN,themail,(int)(linebuf-2)))<=0)
			  break;
		       themail[i]='\0';		  /* terminate it for strchr */
		     }
		    while(!(rstart=strchr(themail,'\n')));
		    i=rstart?i-(++rstart-themail):0;
		    goto no_from;
		  }
		 ;{ size_t tfrl;
		    i-=tfrl= ++rstart-themail;	     /* demarcate From_ line */
		    if(Deliverymode&&override&&!privs)
		     { if(verbose)		  /* discard the bogus From_ */
			  nlog(insufprivs);
		       syslog(LOG_ERR,slogstr,attemptst,fwhom);
		       goto no_from;
		     }
		    if(tstamp)
		       lfr=findtstamp(themail+STRLEN(From_),rstart)
			-themail+tstamp;
		    else if(!fromwhom)	       /* leave the From_ line alone */
		       if(linv)				      /* fake alert? */
			  lfr=tfrl;  /* yes, so separate From_ from the rest */
		       else
			  lfr=0,i+=tfrl;	/* no, tack it onto the rest */
		  }
	       }
	      else
no_from:       { tstamp=0;	   /* no existing From_, so nothing to stamp */
		 if(!fromwhom)					  /* no -f ? */
		    linv=0;			  /* then it can't be a fake */
	       }
	      filled=lfr+linv+i;		    /* From_ + >From_ + rest */
	      if(lfr||linv)	     /* move read text beyond our From_ line */
	       { r= *rstart;tmemmove(themail+lfr+linv,rstart,i);
		 rstart=themail+lfr;	      /* skip the From_ line, if any */
		 if(!linv)				    /* no fake alert */
		  { rstart[-tstamp]='\0';	       /* where do we append */
		    if(!tstamp)		 /* no timestamp, so generate it all */
		       strcat(strcpy(themail,From_),fwhom);	/* From user */
		  }
		 else
		  { if(lfr)			/* did we skip a From_ line? */
		       if(tstamp)	 /* copy the timestamp over the tail */
			  strcpy(rstart-tstamp,buf2);
		       else if(fromwhom)		 /* whole new From_? */
			  strcat(strcat(strcpy(themail,From_),fwhom),buf2);
		    strcat(strcpy(rstart,Fakefield),chp2);     /* fake alert */
		  }			  /* overwrite the trailing \0 again */
		 strcat(themail,buf2);themail[lfr+linv]=r;
	       }
	    }
	 }
	readmail(0,0L);			      /* read in the mail completely */
	if(Deliverymode)
	   do			  /* chp should point to the first recipient */
	    { chp2=chp;
	      if(argv[++argc])			  /* more than one recipient */
		 if(pidchild=sfork())
		  { if(forkerr(pidchild,procmailn)||
		       waitfor(pidchild)!=EXIT_SUCCESS)
		       retvl2=retval;
		    pidchild=0;		      /* loop for the next recipient */
		  }
		 else
		  { newid();
		    while(argv[++argc]);    /* skip till end of command line */
		  }
	    }
	   while(chp=(char*)argv[argc]);
	gargv=argv+argc;suppmunreadable=verbose; /* save it for nextrcfile() */
	if(Deliverymode)	/* try recipient without changing case first */
	 { if(!(pass=auth_finduser(chp2,-1)))	    /* chp2 is the recipient */
	    { static const char unkuser[]="Unknown user";
	      nlog(unkuser);logqnl(chp2);syslog(LOG_ERR,slogstr,unkuser,chp2);
	      return EX_NOUSER;			/* we don't handle strangers */
	    }
	   if(enoughprivs(passinvk,euid,egid,auth_whatuid(pass),
	    auth_whatgid(pass)))
	      goto Setuser;
	   nlog(insufprivs);
	   syslog(LOG_CRIT,"Insufficient privileges to deliver to \"%s\"\n",
	    chp2);
	   return EX_NOPERM;	      /* need more mana, decline the request */
	 }
	else
	 { suppmunreadable=nextrcfile();
	   if(presenviron)		      /* preserving the environment? */
	      etcrc=0;				    /* don't read etcrc then */
	   if(suppmunreadable)			     /* command-line rcfile? */
	      etcrc=0,scomsat=DEFcomsat;	  /* forget etcrc and comsat */
	   if(mailfilter)
	    { if(!suppmunreadable)
	       { nlog("Missing rcfile\n");
		 return EX_NOINPUT;
	       }
#ifdef ETCRCS
	      ;{ static const char etcrcs[]=ETCRCS;
		 if(!strncmp(etcrcs,rcfile,STRLEN(etcrcs)))
		  { struct stat stbuf; /* path starts with /etc/procmailrcs/ */
		   /*
		    *	although the filename starts with /etc/procmailrcs/
		    *	we will now check if it does not contain any backward
		    *	references which would allow it to escape the secure
		    *	tree; look for /../ sequences
		    */
		    for(chp=(char*)rcfile+STRLEN(etcrcs)-1;
			chp;		       /* any devious embedded /../? */
			chp=strpbrk(chp,dirsep))
		       if(!strncmp(pardir,++chp,STRLEN(pardir))&&
			  (chp+=STRLEN(pardir),strchr(dirsep,*chp)))
			  goto nospecial;	  /* yes, security violation */
#ifdef CAN_chown
		    *(chp=strcpy(buf2,etcrcs)+STRLEN(etcrcs))=chCURDIR;
		    *++chp='\0';
#endif
		   /*
		    *	so far, so good, now verify the credentials down to the
		    *	last bit
		    */
		    if(presenviron||			  /* -p is dangerous */
		       suppmunreadable!=2||   /* so are variable assignments */
#ifdef CAN_chown		  /* anyone can chown in this filesystem so: */
		       stat(buf2,&stbuf)||	     /* the /etc/procmailrcs */
		       !S_ISDIR(stbuf.st_mode)||	/* directory must be */
		       stbuf.st_uid!=ROOT_uid&&		    /* owned by root */
			chown(buf2,ROOT_uid,stbuf.st_gid)||
		       stbuf.st_mode&(S_IXGRP|S_IXOTH)&&   /* and accessible */
			chmod(buf2,S_IRWXU)||		   /* to no one else */
#endif
		       lstat(rcfile,&stbuf)||		/* it seems to exist */
		       !enoughprivs(passinvk,euid,egid,stbuf.st_uid,
			stbuf.st_gid)||		   /* can we do this at all? */
		       S_ISDIR(stbuf.st_mode)||		  /* no directories! */
		       !savepass(spassinvk,stbuf.st_uid)     /* user exists? */
		      )
nospecial:	     { static const char densppr[]=
			"Denying special privileges for";
		       nlog(densppr);logqnl(rcfile);
		       syslog(LOG_ALERT,slogstr,densppr,rcfile);
		     }
		    else			    /* no security violation */
		       mailfilter=2,passinvk=spassinvk;
		  }				      /* accept new identity */
	       }
#endif
	    }
	 }
	if(idhint&&(pass=auth_finduser((char*)idhint,0))&&
	    passinvk&&auth_whatuid(passinvk)==auth_whatuid(pass)||
	   (pass=passinvk))
	  /*
	   *	set preferred uid to the intended recipient
	   */
Setuser: { gid=auth_whatgid(pass);uid=auth_whatuid(pass);
	   if(!*(chp=(char*)auth_username(pass)))
	      chp=buf;
	   setdef(lgname,chp);setdef(home,auth_homedir(pass));
	   if(euid==ROOT_uid)
	      initgroups(chp,gid);
	   endgrent();
	   if(!*(chp=(char*)auth_shell(pass)))
	      chp=(char*)binsh;
	   setdef(shell,chp);setdef(orgmail,auth_mailboxname(pass));
	 }
	else		 /* user could not be found, set reasonable defaults */
	  /*
	   *	to prevent security holes, drop any privileges now
	   */
	 { setdef(lgname,buf);setdef(home,RootDir);setdef(shell,binsh);
	   setdef(orgmail,"/tmp/dead.letter");setids();
	 }
	endpwent();auth_freeid(spassinvk);
      }
     if(!presenviron||!mailfilter)	  /* by default override environment */
      { setdef(host,hostname());sputenv(lastfolder);sputenv(exitcode);
	initdefenv();
	;{ const char*const*kp;static const char*const prestenv[]=PRESTENV;
	   for(kp=prestenv;*kp;)	/* preset some environment variables */
	      if(!eputenv(*kp++,buf))
		 setoverflow();
	 }
      }
     /*
      * Processing point of proposed /etc/procmail.conf file
      */
     sgetcp=fdefault;	    /* setup DEFAULT and ORGMAIL and check the spool */
     if(readparse(buf,sgetc,2))		   /* uh, Houston, we have a problem */
      { nlog(orgmail);elog(pathtoolong);elog(newline);
	syslog(LOG_CRIT,"%s%s for LINEBUF for uid \"%lu\"\n",orgmail,
	 pathtoolong,(unsigned long)uid);
	fdefault=empty;
	goto nix_sysmbox;
      }
     fdefault=tstrdup(buf);sgid=egid;
     chp=(char*)getenv(orgmail);
     if(mailfilter||!screenmailbox(chp,egid,Deliverymode))
nix_sysmbox:
      { rcst_nosgid();sputenv(orgmail);	 /* nix delivering to system mailbox */
	if(!strcmp(chp,fdefault))			/* DEFAULT the same? */
	   free((char*)fdefault),fdefault=empty;		 /* so panic */
      }						/* bad news, be conservative */
     doumask(INIT_UMASK);eputenv(defpath,buf);
     while(chp=(char*)argv[argc])      /* interpret command line specs first */
      { argc++;
	if(!asenvcpy(chp)&&mailfilter)
	 { gargv= &nullp;			 /* stop at the first rcfile */
	   for(restargv=argv+argc;restargv[crestarg];crestarg++);
	   break;
	 }
	resettmout();
      }
   }
  ;{ int lastsucc,lastcond,prevcond;
     if(etcrc)		  /* do we start with an /etc/procmailrc file first? */
      { if(0<=bopen(etcrc))
	 { yell(drcfile,etcrc);
#if !DEFverbose
	   if(rcstate!=rc_NORMAL)
	      verbose=0;		    /* no peeking in /etc/procmailrc */
#endif
	   eputenv(defspath,buf);
	   goto startrc;
	 }
	etcrc=0;					     /* no such file */
      }
     do					     /* main rcfile interpreter loop */
      { resettmout();
	if(rc<0)					 /* open new rc file */
	 { struct stat stbuf;
	  /*
	   *	if we happen to be still running as root, and the rcfile
	   *	is mounted on a secure NFS-partition, we might not be able
	   *	to access it, so check if we can stat it or don't need any
	   *	sgid privileges, if yes, drop all privs and set uid to
	   *	the recipient beforehand
	   */
	   goto findrc;
	   do
	    { if(suppmunreadable)	  /* should we supress this message? */
fake_rc:	 readerr(buf);
	      if(!nextrcfile())		      /* not available? try the next */
	       { skiprc=0;
		 goto nomore_rc;
	       }
	      suppmunreadable=0;	      /* keep the current directory, */
findrc:	      i=0;			      /* default rcfile, or neither? */
	      if(rcfile==pmrc&&(i=2))	    /* the default .procmailrc file? */
	       { if(*strcpy((char*)(rcfile=buf2),pmrc2buf())=='\0')
pm_overflow:	  { strcpy(buf,pmrc);
		    goto fake_rc;
		  }
	       }
	      else if(strchr(dirsep,*rcfile)||		   /* absolute path? */
		 (mailfilter||*rcfile==chCURDIR&&strchr(dirsep,rcfile[1]))&&
		 (i=1))				     /* mailfilter or ./ pfx */
		 strcpy(buf,rcfile);	/* do not put anything in front then */
	      else		     /* prepend default procmailrc directory */
	       { *(chp=lastdirsep(pmrc2buf()))='\0';
		 if(buf[0]=='\0')
		    goto pm_overflow;		  /* overflow in pmrc2buf()? */
		 else
		    strcpy(chp,rcfile);			/* append the rcfile */
	       }
	      if(mailfilter!=2&&(rcstate==rc_NOSGID||	 /* nothing special? */
		  !rcstate&&!stat(buf,&stbuf)))	  /* don't need privilege or */
		 setids();		  /* it's accessible?  Transmogrify! */
	    }
	   while(0>bopen(buf));			   /* try opening the rcfile */
	   if(rcstate!=rc_NORMAL&&mailfilter!=2)      /* if it isn't special */
	    { closerc();			    /* but we are, close it, */
	      setids();			  /* transmogrify to prevent peeking */
	      if(0>bopen(buf))				    /* and try again */
		 goto fake_rc;
	    }
#ifndef NOfstat
	   if(fstat(rc,&stbuf))				    /* the right way */
#else
	   if(stat(buf,&stbuf))				  /* the best we can */
#endif
	    { static const char susprcf[]="Suspicious rcfile";
susp_rc:      closerc();nlog(susprcf);logqnl(buf);
	      syslog(LOG_ALERT,slogstr,susprcf,buf);
	      goto fake_rc;
	    }
	   if(mailfilter==2)		 /* change now if we haven't already */
	      setids();
	   erestrict=1;			      /* possibly restrict execs now */
	   if(i==1)		  /* opened rcfile in the current directory? */
	    { if(!didchd)
		 setmaildir(curdir);
	    }
	   else
	     /*
	      * OK, so now we have opened an absolute rcfile, but for security
	      * reasons we only accept it if it is owned by the recipient or
	      * root and is not world writable, and the directory it is in is
	      * not world writable or has the sticky bit set.  If this is the
	      * default rcfile then we also outlaw group writability.
	      */
	    { register char c= *(chp=lastdirsep(buf));
	      if(((stbuf.st_uid!=uid&&stbuf.st_uid!=ROOT_uid||	/* check uid */
		   stbuf.st_mode&S_IWOTH||	       /* writable by others */
		   i&&stbuf.st_mode&S_IWGRP		/* writable by group */
		    &&(NO_CHECK_stgid||stbuf.st_gid!=gid)
		  )&&strcmp(devnull,buf)||    /* /dev/null is a special case */
		 (*chp='\0',stat(buf,&stbuf))||	      /* check the directory */
#ifndef CAN_chown				   /* sticky and can't chown */
		 !(stbuf.st_mode&S_ISVTX)&&	   /* means we don't care if */
#endif					     /* it's group or world writable */
		 ((stbuf.st_mode&(S_IWOTH|S_IXOTH))==(S_IWOTH|S_IXOTH)||
		  i&&(stbuf.st_mode&(S_IWGRP|S_IXGRP))==(S_IWGRP|S_IXGRP)
		   &&(NO_CHECK_stgid||stbuf.st_gid!=gid))))
	       { *chp=c;
		 goto susp_rc;
	       }
	      *chp=c;
	    }
	  /*
	   *	set uid back to recipient in any case, since we might just
	   *	have opened his/her .procmailrc (don't remove these, since
	   *	the rcfile might have been created after the first stat)
	   */
	   yell(drcfile,buf);
	   if(!didchd)			       /* have we done this already? */
	    { if((chp=lastdirsep(pmrc2buf()))>buf+1)	/* not the root dir? */
		 chp--;
	      *chp='\0';		     /* eliminate trailing separator */
	      if(chp==buf)					   /* arrrg! */
	       { nlog("procmailrc");elog(pathtoolong);elog(newline);
		 syslog(LOG_CRIT,"procmailrc%s for LINEBUF for uid \"%lu\"\n",
		  pathtoolong,(unsigned long)uid);
		 goto nomore_rc;		    /* just save it and pray */
	       }
	      if(chdir(chp=buf))      /* no, well, then try an initial chdir */
	       { chderr(buf);
		 if(chdir(chp=(char*)tgetenv(home)))
		    chderr(chp),chp=(char*)curdir;
	       }
	      setmaildir(chp);
	    }
startrc:   lastsucc=lastcond=prevcond=0;
	 }
	unlock(&loclock);			/* unlock any local lockfile */
	goto commint;
	do
	 { skipline();
commint:   do skipspace();				  /* skip whitespace */
	   while(testB('\n'));
	 }
	while(testB('#'));				   /* no comment :-) */
	if(testB(':'))				       /* check for a recipe */
	 { int locknext,succeed;char*startchar;long tobesent;
	   static char flags[maxindex(exflags)];
	   do
	    { int nrcond;
	      if(readparse(buf,getb,0))
		 goto nextrc;			      /* give up on this one */
	      ;{ char*chp3;
		 nrcond=strtol(buf,&chp3,10);chp=chp3;
	       }
	      if(chp==buf)				 /* no number parsed */
		 nrcond= -1;
	      if(tolock)	 /* clear temporary buffer for lockfile name */
		 free(tolock);
	      for(i=maxindex(flags);flags[i]=0,i--;);	  /* clear the flags */
	      for(tolock=0,locknext=0;;)
	       { chp=skpspace(chp);
		 switch(i= *chp++)
		  { default:
		       if(!(chp2=strchr(exflags,i)))	    /* a valid flag? */
			{ chp--;
			  break;
			}
		       flags[chp2-exflags]=1;		     /* set the flag */
		    case '\0':
		       if(chp!=Tmnate)		/* if not the real end, skip */
			  continue;
		       break;
		    case ':':locknext=1;    /* yep, local lockfile specified */
		       if(*chp||++chp!=Tmnate)
			  tolock=tstrdup(chp),chp=strchr(chp,'\0')+1;
		  }
		 concatenate(chp);skipped(chp);		/* display leftovers */
		 break;
	       }			      /* parse & test the conditions */
	      i=conditions(flags,prevcond,lastsucc,lastcond,nrcond);
	      if(!skiprc)
	       { if(!flags[ALSO_NEXT_RECIPE]&&!flags[ALSO_N_IF_SUCC])
		    lastcond=i==1;	   /* save the outcome for posterity */
		 if(!prevcond||!flags[ELSE_DO])
		    prevcond=i==1;    /* ditto for `else if' like constructs */
	       }
	    }
	   while(i==2);			     /* missing in action, reiterate */
	   startchar=themail;tobesent=filled;
	   if(flags[PASS_HEAD])			    /* body, header or both? */
	    { if(!flags[PASS_BODY])
		 tobesent=thebody-themail;
	    }
	   else if(flags[PASS_BODY])
	      tobesent-=(startchar=thebody)-themail;
	   Stdout=0;succeed=sh=0;
	   pwait=flags[WAIT_EXIT]|flags[WAIT_EXIT_QUIET]<<1;
	   ignwerr=flags[IGNORE_WRITERR];skipspace();
	   if(i)
	      zombiecollect(),concon('\n');
progrm:	   if(testB('!'))				 /* forward the mail */
	    { char*fencepost=buf+linebuf-1;
	      if(!i)
		 skiprc|=1;
	      *fencepost='\0';
	      strncpy(buf,sendmail,linebuf-1);
	      if((chp=strchr(buf,'\0'))==fencepost)
		 goto fail;
	      if(*flagsendmail)
	       { char*q;int got=0;
		 if(!(q=simplesplit(chp+1,flagsendmail,fencepost,&got)))
		    goto fail;
		 *(chp=q)='\0';
	       }
	      if(readparse(chp+1,getb,0))
		 goto fail;
	      if(i)
	       { if(startchar==themail)
		  { startchar[filled]='\0';		     /* just in case */
		    startchar=(char*)skipFrom_(startchar,&tobesent);
		  }   /* leave off leading From_ -- it confuses some mailers */
		 goto forward;
	       }
	      skiprc--;
	    }
	   else if(testB('|'))				    /* pipe the mail */
	    { chp=buf2;
	      if(getlline(chp,buf2+linebuf))	 /* get the command to start */
		 goto commint;
	      if(i)
	       { metaparse(buf2);
		 if(!sh&&buf+1==Tmnate)		      /* just a pipe symbol? */
		  { *buf='|';*(char*)(Tmnate++)='\0';		  /* fake it */
		    goto tostdout;
		  }
forward:	 if(locknext)
		  { if(!tolock)	   /* an explicit lockfile specified already */
		     { *buf2='\0';  /* find the implicit lockfile ('>>name') */
		       for(chp=buf;i= *chp++;)
			  if(i=='>'&&*chp=='>')
			   { chp=skpspace(chp+1);
			     tmemmove(buf2,chp,i=strcspn(chp,EOFName));
			     buf2[i]='\0';
			     if(sh)	 /* expand any environment variables */
			      { chp=tstrdup(buf);sgetcp=buf2;
				if(readparse(buf,sgetc,0))
				 { *buf2='\0';
				   goto nolock;
				 }
				strcpy(buf2,buf);strcpy(buf,chp);free(chp);
			      }
			     break;
			   }
		       if(!*buf2)
nolock:			{ nlog("Couldn't determine implicit lockfile from");
			  logqnl(buf);
			}
		     }
		    lcllock();
		    if(!pwait)		/* try and protect the user from his */
		       pwait=2;			   /* blissful ignorance :-) */
		  }
		 rawnonl=flags[RAW_NONL];inittmout(buf);asgnlastf=1;
		 if(flags[CONTINUE]&&(flags[FILTER]||Stdout))
		    nlog(extrns),elog("copy-flag"),elog(ignrd);
		 if(flags[FILTER])
		  { if(startchar==themail&&tobesent!=filled)  /* if only 'h' */
		     { if(!pipthrough(buf,startchar,tobesent))
			  readmail(1,tobesent),succeed=!pipw;
		     }
		    else if(!pipthrough(buf,startchar,tobesent))
		       filled=startchar-themail,readmail(0,0L),succeed=!pipw;
		  }
		 else if(Stdout)		  /* capturing stdout again? */
		  { if(!pipthrough(buf,startchar,tobesent))
		       succeed=1,postStdout();	  /* only parse if no errors */
		  }
		 else if(!pipin(buf,startchar,tobesent))  /* regular program */
		    if(succeed=1,flags[CONTINUE])
		       goto logsetlsucc;
		    else
		       goto frmailed;
		 goto setlsucc;
	       }
	    }
	   else if(testB(EOF))
	      nlog("Incomplete recipe\n");
	   else		   /* dump the mail into a mailbox file or directory */
	    { int ofiltflag;char*end=buf+linebuf-4;	/* reserve some room */
	      if(ofiltflag=flags[FILTER])
		 flags[FILTER]=0,nlog(extrns),elog("filter-flag"),elog(ignrd);
	      if(chp=gobenv(buf,end))	   /* can it be an environment name? */
	       { if(chp==end)
		  { getlline(buf,buf+linebuf);
		    goto fail;
		  }
		 if(skipspace())
		    chp++;		   /* keep pace with argument breaks */
		 if(testB('='))		      /* is it really an assignment? */
		  { int c;
		    *chp++='=';*chp='\0';
		    if(skipspace())
		       chp++;
		    ungetb(c=getb());
		    switch(c)
		     { case '!':case '|':		  /* ok, it's a pipe */
			  if(i)
			     Stdout = tstrdup(buf);
			  goto progrm;
		     }
		  }
	       }		 /* find the end, start of a nesting recipe? */
	      else if((chp=strchr(buf,'\0'))==buf&&
		      testB('{')&&
		      (*chp++='{',*chp='\0',testB(' ')||
		       testB('\t')||
		       testB('\n')))
	       { if(locknext&&!flags[CONTINUE])
		    nlog(extrns),elog("locallockfile"),elog(ignrd);
		 if(flags[PASS_BODY])
		    nlog(extrns),elog("deliver-body flag"),elog(ignrd);
		 if(flags[PASS_HEAD])
		    nlog(extrns),elog("deliver-head flag"),elog(ignrd);
		 if(flags[IGNORE_WRITERR])
		    nlog(extrns),elog("ignore-write-error flag"),elog(ignrd);
		 if(flags[RAW_NONL])
		    nlog(extrns),elog("raw-mode flag"),elog(ignrd);
		 if(!i)						/* no match? */
		    skiprc+=2;		      /* increase the skipping level */
		 else
		  { app_vali(ifstack,prevcond);		    /* push prevcond */
		    app_vali(ifstack,lastcond);		    /* push lastcond */
		    if(locknext)
		     { *buf2='\0';lcllock();
		       if(!pwait)	/* try and protect the user from his */
			  pwait=2;		   /* blissful ignorance :-) */
		     }
		    succeed=1;
		    if(flags[CONTINUE])
		     { yell("Forking",procmailn);inittmout(procmailn);
		       onguard();
		       if(!(pidchild=sfork()))		   /* clone yourself */
			{ if(loclock)	      /* lockfiles are not inherited */
			     free(loclock),loclock=0;
			  if(globlock)
			     free(globlock),globlock=0;	     /* clear up the */
			  newid();offguard();duprcs();	  /* identity crisis */
			}
		       else
			{ offguard();
			  if(forkerr(pidchild,procmailn))
			     succeed=0;	       /* tsk, tsk, no cloning today */
			  else
			   { int excode;  /* wait for our significant other? */
			     if(pwait&&
				(excode=waitfor(pidchild))!=EXIT_SUCCESS)
			      { if(!(pwait&2)||verbose)	 /* do we report it? */
				   progerr(procmailn,excode,pwait&2);
				succeed=0;
			      }
			     pidchild=0;skiprc+=2;   /* skip over the braces */
			     ifstack.filled-=2;		/* retract the stack */
			   }
			}
		     }
		    goto jsetlsucc;		/* definitely no logabstract */
		  }
		 continue;
	       }
	      if(!i)						/* no match? */
		 skiprc|=1;		  /* temporarily disable subprograms */
	      if(readparse(chp,getb,0))
fail:	       { succeed=0;setoverflow();
		 goto setlsucc;
	       }
	      if(i)
	       { if(ofiltflag)	       /* protect who use bogus filter-flags */
		    startchar=themail,tobesent=filled;	    /* whole message */
tostdout:	 rawnonl=flags[RAW_NONL];
		 if(locknext)		     /* write to a file or directory */
		  { if(!tolock)strcpy(buf2,buf);
		    lcllock();
		  }
		 inittmout(buf);	  /* to break messed-up kernel locks */
		 if(writefolder(buf,strchr(buf,'\0')+1,startchar,tobesent,
		     ignwerr,0)&&
		    (succeed=1,!flags[CONTINUE]))
frmailed:	  { if(ifstack.vals)
		       free(ifstack.vals);
		    goto mailed;
		  }
logsetlsucc:	 if(succeed&&flags[CONTINUE]&&lgabstract==2)
		    logabstract(tgetenv(lastfolder));
setlsucc:	 rawnonl=0;
jsetlsucc:	 lastsucc=succeed;lasttell= -1;		       /* for comsat */
	       }
	      skiprc&=~1;			     /* reenable subprograms */
	    }
	 }
	else if(testB('}'))					/* end block */
	 { if(skiprc>1)					    /* just skipping */
	      skiprc-=2;				   /* decrease level */
	   else if(ifstack.filled>ifdepth)    /* restore lastcond from stack */
	    { lastcond=acc_vali(ifstack,--ifstack.filled);
	      prevcond=acc_vali(ifstack,--ifstack.filled);	 /* prevcond */
	    }							  /* as well */
	   else
	      nlog("Closing brace unexpected\n");	      /* stack empty */
	 }
	else				    /* then it must be an assignment */
	 { char*end=buf+linebuf;
	   if(!(chp=gobenv(buf,end)))
	    { if(!*buf)					/* skip a word first */
		 getbl(buf,end);			      /* then a line */
	      skipped(buf);				/* display leftovers */
	      continue;
	    }
	   if(chp==end)				      /* overflow => give up */
nextrc:	      if(poprc()||wipetcrc())
		 continue;
	      else
		 break;
	   skipspace();
	   if(testB('='))			   /* removal or assignment? */
	    { *chp='=';
	      if(readparse(++chp,getb,1))
	       { setoverflow();
		 continue;
	       }
	    }
	   else
	      *++chp='\0';		     /* throw in a second terminator */
	   if(!skiprc)
	      chp2=(char*)sputenv(buf),chp[-1]='\0',asenv(chp2);
	 }
      }						    /* main interpreter loop */
     while(rc<0||!testB(EOF)||poprc()||wipetcrc());
   }
nomore_rc:
  if(ifstack.vals)
     free(ifstack.vals);
  ;{ int succeed;
     concon('\n');succeed=0;
     if(*(chp=(char*)fdefault))				     /* DEFAULT set? */
      { int len;
	setuid(uid);			   /* make sure we have enough space */
	if(linebuf<(len=strlen(chp)+strlen(lockext)+UNIQnamelen))
	   mallocbuffers(linebuf=len,1);   /* to perform the lock & delivery */
	if(writefolder(chp,(char*)0,themail,filled,0,1))	  /* default */
	   succeed=1;
      }						       /* if all else failed */
     if(!succeed&&*(chp2=(char*)tgetenv(orgmail))&&strcmp(chp2,chp))
      { rawnonl=0;
	if(writefolder(chp2,(char*)0,themail,filled,0,0))     /* don't panic */
	   succeed=1;				      /* try the last resort */
      }
     if(succeed)				     /* should we panic now? */
mailed: rawnonl=0,retval=EXIT_SUCCESS;	  /* we're home free, mail delivered */
   }
  unlock(&loclock);Terminate();
}

int eqFrom_(a)const char*const a;
{ return !strncmp(a,From_,STRLEN(From_));
}

const char*skipFrom_(startchar,tobesentp)const char*startchar;long*tobesentp;
{ if(eqFrom_(startchar))
   { long tobesent;char i;
     tobesent= *tobesentp;
     do
	while(i= *startchar++,--tobesent&&i!='\n');
     while(*startchar=='>');
     *tobesentp=tobesent;
   }
  return startchar;
}
