procmail.c   [plain text]


/************************************************************************
 *	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-2001, Philip Guenther, The United States	*
 *							of America	*
 *	#include "../README"						*
 ************************************************************************/
#ifdef RCS
static /*const*/char rcsid[]=
 "$Id: procmail.c,v 1.183 2001/08/31 04:57:36 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 "memblk.h"
#include "pipes.h"
#include "common.h"
#include "cstdio.h"
#include "exopen.h"
#include "mcommon.h"
#include "goodies.h"
#include "locking.h"
#include "mailfold.h"
#include "lastdirsep.h"
#include "authenticate.h"
#include "lmtp.h"
#include "foldinfo.h"
#include "variables.h"
#include "comsat.h"
#include "from.h"

static const char*const nullp,exflags[]=RECFLAGS,drcfile[]="Rcfile:",
 pmusage[]=PM_USAGE,*etcrc=ETCRC,misrecpt[]="Missing recipient\n",
 extrns[]="Extraneous ",ignrd[]=" ignored\n",pardir[]=chPARDIR,
 defspath[]=DEFSPATH,defmaildir[]=DEFmaildir;
char*buf,*buf2,*loclock;
const char shell[]="SHELL",lockfile[]="LOCKFILE",newline[]="\n",binsh[]=BinSh,
 unexpeof[]="Unexpected EOL\n",*const*gargv,*const*restargv= &nullp,*sgetcp,
 pmrc[]=PROCMAILRC,*rcfile,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,curdir[]={chCURDIR,'\0'},
 slogstr[]="%s \"%s\"",conflicting[]="Conflicting ",orgmail[]="ORGMAIL",
 insufprivs[]="Insufficient privileges\n",defpath[]=DEFPATH,
 exceededlb[]="Exceeded LINEBUF\n",errwwriting[]="Error while writing to",
 Version[]=VERSION;
int retval=EX_CANTCREAT,retvl2=EXIT_SUCCESS,sh,pwait,rc= -1,
 privileged=priv_START,lexitcode=EXIT_SUCCESS,ignwerr,crestarg,savstdout,
 berkeley,mailfilter,erestrict,Deliverymode,ifdepth;   /* depth of outermost */
struct dyna_array ifstack;
size_t linebuf=mx(DEFlinebuf,1024/*STRLEN(systm_mbox)<<1*/);
volatile int nextexit,lcking;		       /* if termination is imminent */
pid_t thepid;
long filled,lastscore;	       /* the length of the mail, and the last score */
memblk themail;							 /* the mail */
char*thebody;					  /* the body of the message */
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;
}

#define rct_ABSOLUTE	0			    /* rctypes for tryopen() */
#define rct_CURRENT	1
#define rct_DEFAULT	2

#define rcs_DELIVERED	1		     /* rc exit codes for mainloop() */
#define rcs_EOF		2
#define rcs_HOST	3

static void
 usage P((void));
static int
 tryopen P((const int delay_setid,const int rctype,const int dowarning)),
 mainloop P((void));

int main(argc,argv)int argc;const char*const argv[];
{ register char*chp,*chp2;
#if 0				/* enable this if you want to trace procmail */
  kill(getpid(),SIGSTOP);/*raise(SIGSTOP);*/
#endif
  newid();
  ;{ int presenviron,override;char*fromwhom=0;
     const char*idhint=0;gid_t egid=getegid();
     presenviron=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(argc=0;(chp2=(char*)argv[++argc])&&*chp2=='-';)
	   for(;;)				       /* processing options */
	    { switch(*++chp2)
	       { case VERSIONOPT:
		    usage();
		    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 struct dyna_array newargv;
		    if(*++chp2)
		       goto setarg;
		    else if(chp2=(char*)argv[argc+1])
		     { argc++;
setarg:		       app_valp(newargv,(const char*)chp2);
		       restargv=&(acc_valp(newargv,0));
		       crestarg++;
		     }
		    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 LMTPOPT:
#ifdef LMTP
		    Deliverymode=2;
		    goto last_option;
#else
		    nlog("LMTP support not enabled in this binary\n");
		    return EX_USAGE;
#endif
		 case '-':
		    if(!*++chp2)
		     { argc++;
		       goto last_option;
		     }
		 default:nlog("Unrecognised options:");logqnl(chp2);
		    elog(pmusage);elog("Processing continued\n");
		 case '\0':;
	       }
	      break;
	    }
      }
     if(Deliverymode==1&&!(chp=chp2))
	nlog(misrecpt),Deliverymode=0;
last_option:
     switch(Deliverymode)
      { case 0:
	   idhint=getenv(lgname);
	   if(mailfilter&&crestarg)
	    { crestarg=0;			     /* -m will supersede -a */
conflopt:     nlog(conflicting);elog("options\n");elog(pmusage);
	    }
	   break;
#ifdef LMTP
	case 2:
	   if(fromwhom)
	    { fromwhom=0;				  /* -z disables -f, */
	      goto confldopt;					/* -p and -m */
	    }
#endif
	case 1:
	   if(presenviron||mailfilter)
confldopt:  { presenviron=mailfilter=0;		    /* -d disables -p and -m */
	      goto conflopt;
	    }
	   break;
	default:				       /* this cannot happen */
	   abort();
      }
     cleanupenv(presenviron);
     ;{ auth_identity*pass,*passinvk;auth_identity*spassinvk;
	uid_t euid=geteuid();
	uid=getuid();gid=getgid();
	spassinvk=auth_newid();passinvk=savepass(spassinvk,uid);   /* are we */
	checkprivFrom_(euid,passinvk?auth_username(passinvk):0,override);
	doumask(INIT_UMASK);		   /* allowed to set the From_ line? */
	while((savstdout=rdup(STDOUT))<=STDERR)
	 { rclose(savstdout);		       /* move stdout out of the way */
	   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 /dev/null */
	 }
#ifdef console
	opnlog(console);
#endif
	setbuf(stdin,(char*)0);allocbuffers(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);
	setcomsat(empty);			  /* turn on biff by default */
	ultstr(0,(unsigned long)uid,buf);filled=0;
	if(!passinvk||!(chp2=(char*)auth_username(passinvk)))
	   chp2=buf;
#ifdef LMTP
	if(Deliverymode==2)
	 { auth_identity**rcpts,**lastrcpt,**currcpt;
	   currcpt=rcpts=lmtp(&lastrcpt,chp2);
	   if(currcpt+1!=lastrcpt)     /* if there's more than one recipient */
	      lockblock(&themail);     /* then no one can write to the block */
	   else				     /* otherwise the only recipient */
	      private(1);				/* gets the original */
	   while(currcpt<lastrcpt)
	    { if(!(pidchild=sfork()))
	       { setupsigs();
		 pass= *currcpt++;
		 while(currcpt<lastrcpt)
		    auth_freeid(*currcpt++);
		 freeoverread();
		 free(rcpts);newid();gargv=&nullp;
		 goto dorcpt;
	       }
	      if(forkerr(pidchild,procmailn))
		 lexitcode=EX_OSERR;
	      else
		 waitfor(pidchild);
	      lmtpresponse(lexitcode);
	      pidchild=0;
	      auth_freeid(*currcpt++);
	    }
	   free(rcpts);
	   flushoverread();		 /* pass upwards the extra LMTP data */
	   exit(EXIT_SUCCESS);
	 }
#endif
	setupsigs();
	makeFrom(fromwhom,chp2);
	readmail(0,0L);			      /* read in the mail completely */
	if(Deliverymode)
	 { if(argv[argc+1])			 /* more than one recipient? */
	    { private(0);				    /* time to share */
	      lockblock(&themail);
	    }
	   else
	      private(1);
	   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 */
		    break;
		  }
	    }
	   while(chp=(char*)argv[argc]);
	 }
	gargv=argv+argc;			 /* 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 */
	    }
dorcpt:	   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
	 { int commandlinerc=nextrcfile();
	   if(presenviron)		      /* preserving the environment? */
	      etcrc=0;				    /* don't read etcrc then */
	   if(commandlinerc)			     /* command-line rcfile? */
	    { etcrc=0;				 /* forget etcrc and comsat: */
	      setcomsat(DEFcomsat);			/* the internal flag */
	      if(!presenviron)			 /* and usually the variable */
		 setdef(scomsat,DEFcomsat);
	    }
	   if(mailfilter)
	    { if(!commandlinerc)
	       { 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 */
		       commandlinerc!=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
	    }
	 }
	pass=passinvk;
	if(passinvk&&idhint)	      /* if same uid as $LOGNAME, use latter */
	 { auth_identity*idpass=auth_finduser((char*)idhint,0);
	   if(idpass)
	    { if(auth_whatuid(passinvk)==auth_whatuid(idpass))
		 pass=idpass;
	    }
	 }
	if(pass)	      /* set preferred uid to the intended recipient */
Setuser: { gid=auth_whatgid(pass);uid=auth_whatuid(pass);
	   if(euid==ROOT_uid&&(chp=(char*)auth_username(pass))&&*chp)
	      initgroups(chp,gid);
	   endgrent();
	 }
	else					  /* user could not be found */
	   setids();   /* to prevent security holes, drop any privileges now */
	initdefenv(pass,buf,!presenviron||!mailfilter);		 /* override */
	endpwent();auth_freeid(spassinvk);	   /* environment by default */
      }
     if(buildpath(orgmail,fdefault,(char*)0))	/* setup DEFAULT and ORGMAIL */
      { fdefault=empty;			   /* uh, Houston, we have a problem */
	goto nix_sysmbox;
      }
     /*
      * Processing point of proposed /etc/procmail.conf file
      */
     fdefault=tstrdup(buf);sgid=egid;
     chp=(char*)tgetenv(orgmail);
     if(mailfilter||!screenmailbox(chp,egid,Deliverymode))
nix_sysmbox:
      { sputenv(orgmail);		 /* nix delivering to system mailbox */
	if(privileged)			       /* don't need this any longer */
	   privileged=priv_DONTNEED;
	if(!strcmp(chp,fdefault))			/* DEFAULT the same? */
	   free((char*)fdefault),fdefault=empty;		 /* so panic */
      }						/* bad news, be conservative */
   }
  doumask(INIT_UMASK);
  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;
      }
   }
  if(etcrc)		  /* do we start with an /etc/procmailrc file first? */
   { if(0<=bopen(etcrc))
      { yell(drcfile,etcrc);
#if !DEFverbose
	if(privileged)
	   verbose=0;			    /* no peeking in /etc/procmailrc */
#endif
	eputenv(defspath,buf);			      /* use the secure PATH */
	if(mainloop()==rcs_DELIVERED)			   /* run the rcfile */
	   goto mailed;
	eputenv(defpath,buf);		 /* switch back to the insecure PATH */
      }
   }
  erestrict=mailfilter!=2;		      /* possibly restrict execs now */
  if(rcfile&&!mailfilter)			     /* "procmail rcfile..." */
   { int rctype,dowarning;	 /* only warn about the first missing rcfile */
     for(dowarning=1;;)
      { rctype=rct_ABSOLUTE;
	if(strchr(dirsep,*rcfile))				/* absolute? */
	   strcpy(buf,rcfile);
	else if(*rcfile==chCURDIR&&strchr(dirsep,rcfile[1]))   /* ./ prefix? */
	   strcpy(buf,rcfile),rctype=rct_CURRENT;
	else			     /* prepend default procmailrc directory */
	   if(buildpath(maildir,defmaildir,rcfile))
	      break;
	if(tryopen(0,rctype,dowarning))
	 { register int rcs=mainloop();				   /* run it */
	   if(rcs==rcs_DELIVERED)
	      goto mailed;					 /* success! */
	   if(rcs==rcs_EOF)
	      break;				     /* normal end of rcfile */
	   if(!nextrcfile())				       /* none left? */
	      goto mailed;					 /* then out */
	 }
	else				      /* not available? try the next */
	 { dowarning=0;				/* suppress further messages */
	   if(!nextrcfile())				       /* none left? */
	      break;						 /* then out */
	 }
      }
   }
  else
   { int rctype;
     if(!rcfile)			    /* no rcfile on the command line */
      { rctype=rct_DEFAULT;
	if(buildpath("default rcfile",pmrc,(char*)0))
	   goto nomore_rc;
      }
     else						  /* mailfilter mode */
      { rctype=strchr(dirsep,*rcfile)?rct_ABSOLUTE:rct_CURRENT;
	strcpy(buf,rcfile);
      }
     if(tryopen(mailfilter==2,rctype,DEFverbose||mailfilter))
	if(mainloop()!=rcs_EOF)
	   goto mailed;
   }
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))
	   allocbuffers(linebuf=len,1);	   /* to perform the lock & delivery */
	if(writefolder(chp,(char*)0,themail.p,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.p,filled,0,0))   /* don't panic */
	   succeed=1;				      /* try the last resort */
      }
     if(succeed)				     /* should we panic now? */
mailed: retval=EXIT_SUCCESS;		  /* we're home free, mail delivered */
   }
  unlock(&loclock);Terminate();
}

static void usage P((void))
{ 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);
#ifdef USE_MMAP
  elog("\nLarge messages will be memory mapped during processing.\n");
#endif
}

/*
 *	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
 */
static int tryopen(delay_setid,rctype,dowarning)
 const int delay_setid,rctype,dowarning;
{ struct stat stbuf;
  if(!delay_setid&&privileged&&	  /* if we can setid now and we haven't yet, */
      (privileged==priv_DONTNEED||!stat(buf,&stbuf))) /* and we either don't */
     setids();	   /* need the privileges or it's accessible, then setid now */
  if(0>bopen(buf))				   /* try opening the rcfile */
   { if(dowarning)
rerr:	readerr(buf);
     return 0;
   }
  if(!delay_setid&&privileged)		   /* if we're not supposed to delay */
   { closerc();		       /* and we haven't changed yet, then close it, */
     setids();				 /* transmogrify to prevent peeking, */
     if(0>bopen(buf))					    /* and try again */
	goto rerr;		   /* they couldn't read it, so it was bogus */
   }
#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";
suspicious_rc:
     closerc();nlog(susprcf);logqnl(buf);
     syslog(LOG_ALERT,slogstr,susprcf,buf);
     goto rerr;
   }
  if(delay_setid)			 /* change now if we haven't already */
     setids();
  if(rctype==rct_CURRENT)	  /* 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*chp=lastdirsep(buf),c;
     c= *chp;
     if(((stbuf.st_uid!=uid&&stbuf.st_uid!=ROOT_uid||	       /* check uid, */
	  (stbuf.st_mode&S_IWOTH)||		      /* writable by others, */
	  rctype==rct_DEFAULT&&		   /* if the default then also check */
	   (stbuf.st_mode&S_IWGRP)&&		   /* for 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)||
	 rctype==rct_DEFAULT&&
	  (stbuf.st_mode&(S_IWGRP|S_IXGRP))==(S_IWGRP|S_IXGRP)&&
	  (NO_CHECK_stgid||stbuf.st_gid!=gid))))
      { *chp=c;
	goto suspicious_rc;
      }
     *chp=c;
   }
  yell(drcfile,buf);
  /*
   *	Chdir now if we haven't already
   */
  if(!didchd)				       /* have we done this already? */
   { const char*chp;
     if(buildpath(maildir,defmaildir,(char*)0))
	exit(EX_OSERR);		   /* something was wrong: give up the ghost */
     if(chdir(chp=buf))
      { chderr(buf);		      /* no, well, then try an initial chdir */
	chp=tgetenv(home);
	if(!strcmp(chp,buf)||chdir(chp))
	   chderr(chp),chp=curdir;		/* that didn't work, use "." */
      }
     setmaildir(chp);
   }
  return 1;						 /* we're good to go */
}

static int mainloop P((void))
{ int lastsucc,lastcond,prevcond,i,skiprc;register char*chp,*tolock;
  lastsucc=lastcond=prevcond=skiprc=0;
  tolock=0;
  do
   { 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,skiprc))
	      return rcs_EOF;			      /* give up on this one */
	   ;{ char*temp;			 /* so that chp isn't forced */
	      nrcond=strtol(buf,&temp,10);chp=temp;	      /* into memory */
	    }
	   if(chp==buf)					 /* no number parsed */
	      nrcond= -1;
	   if(tolock)		 /* clear temporary buffer for lockfile name */
	      free(tolock);
	   for(i=maxindex(flags);i;i--)			  /* clear the flags */
	      flags[i]=0;
	   for(tolock=0,locknext=0;;)
	    { chp=skpspace(chp);
	      switch(i= *chp++)
	       { default:
		    ;{ char*flg;
		       if(!(flg=strchr(exflags,i)))	    /* a valid flag? */
			{ chp--;
			  break;
			}
		       flags[flg-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,skiprc!=0,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.p;tobesent=filled;
	if(flags[PASS_HEAD])			    /* body, header or both? */
	 { if(!flags[PASS_BODY])
	      tobesent=thebody-themail.p;
	 }
	else if(flags[PASS_BODY])
	   tobesent-=(startchar=thebody)-themail.p;
	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 */
	 { if(!i)
	      skiprc|=1;
	   if(strlcpy(buf,sendmail,linebuf)>=linebuf)
	      goto fail;
	   chp=strchr(buf,'\0');
	   if(*flagsendmail)
	    { char*q;int got=0;
	      if(!(q=simplesplit(chp+1,flagsendmail,buf+linebuf-1,&got)))
		 goto fail;
	      *(chp=q)='\0';
	    }
	   if(readparse(chp+1,getb,0,skiprc))
	      goto fail;
	   if(i)
	    { if(startchar==themail.p)
	       { startchar[filled]='\0';		     /* just in case */
		 startchar=(char*)skipFrom_(startchar,&tobesent);
	       }      /* leave off leading From_ -- it confuses some mailers */
	      goto forward;
	    }
	   skiprc&=~1;
	 }
	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,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(tolock,buf2);
		 if(!pwait)		/* try and protect the user from his */
		    pwait=2;			   /* blissful ignorance :-) */
	       }
	      rawnonl=flags[RAW_NONL];
	      if(flags[CONTINUE]&&(flags[FILTER]||Stdout))
		 nlog(extrns),elog("copy-flag"),elog(ignrd);
	      inittmout(buf);
	      if(flags[FILTER])
	       { if(startchar==themail.p&&tobesent!=filled)   /* if only 'h' */
		  { if(!pipthrough(buf,startchar,tobesent))
		       readmail(1,tobesent),succeed=!pipw;
		  }
		 else if(!pipthrough(buf,startchar,tobesent))
		  { filled=startchar-themail.p;
		    readmail(0,tobesent);
		    succeed=!pipw;
		  }
	       }
	      else if(Stdout)			  /* capturing stdout again? */
		 succeed=!pipthrough(buf,startchar,tobesent);
	      else if(!pipin(buf,startchar,tobesent,1))	  /* regular program */
	       { succeed=1;
		 if(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)
		  { lcllock(tolock,"");
		    if(!pwait)		/* try and protect the user from his */
		       pwait=2;			   /* blissful ignorance :-) */
		  }
		 succeed=1;
		 if(flags[CONTINUE])
		  { yell("Forking",procmailn);
		    private(0);			      /* can't share anymore */
		    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 setlsucc;			/* definitely no logabstract */
	       }
	      continue;
	    }
	   if(!i)						/* no match? */
	      skiprc|=1;		  /* temporarily disable subprograms */
	   if(readparse(chp,getb,0,skiprc))
fail:	    { succeed=0;
	      goto setlsucc;
	    }
	   if(i)
	    { if(ofiltflag)	       /* protect who use bogus filter-flags */
		 startchar=themail.p,tobesent=filled;	    /* whole message */
tostdout:     rawnonl=flags[RAW_NONL];
	      if(locknext)		     /* write to a file or directory */
		 lcllock(tolock,buf);
	      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);
		 return rcs_DELIVERED;
	       }
logsetlsucc:  if(succeed&&flags[CONTINUE]&&lgabstract==2)
		 logabstract(tgetenv(lastfolder));
setlsucc:     rawnonl=0;lastsucc=succeed;lasttell= -1;	       /* for comsat */
	      resettmout();			  /* clear any pending timer */
	    }
	   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 */
	   break;
	skipspace();
	if(testB('='))				   /* removal or assignment? */
	 { *chp='=';
	   if(readparse(++chp,getb,1,skiprc))
	      continue;
	 }
	else
	   *++chp='\0';			     /* throw in a second terminator */
	if(!skiprc)
	 { const char*p;
	   p=sputenv(buf);
	   chp[-1]='\0';
	   asenv(p);
	 }
      }
     if(rc<0)				   /* abnormal exit from the rcfile? */
	return rcs_HOST;
   }
  while(!testB(EOF)||(skiprc=0,poprc()));
  return rcs_EOF;
}