/************************************************************************ * formail - The mail (re)formatter * * * * Seems to be relatively bug free. * * * * Copyright (c) 1990-2000, 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: formail.c,v 1.102 2001/08/04 07:07:43 guenther Exp $"; #endif static /*const*/char rcsdate[]="$Date: 2001/08/04 07:07:43 $"; #include "includes.h" #include /* iscntrl() */ #include "formail.h" #include "acommon.h" #include "sublib.h" #include "shell.h" #include "common.h" #include "fields.h" #include "ecommon.h" #include "formisc.h" #include "../patchlevel.h" #define ssl(str) str,STRLEN(str) #define bsl(str) {ssl(str)} #define sslbar(str,bar1,bar2) {ssl(str),STRLEN(bar1)-1,STRLEN(bar2)-1} static const char #define X(name,value) name[]=value, #include "header.h" /* pull in the definitions */ #undef X From_[]= FROM, /* VNIX 'From ' line */ Article_[]= "Article ", /* USENET 'Article ' line */ x_[]= "X-", /* general extension */ old_[]= OLD_PREFIX, /* my extension */ xloop[]= "X-Loop:", /* ditto ... */ Resent_[]= "Resent-", /* for tweaking reply preferences */ mdaemon[]="<>",unknown[]=UNKNOWN,re[]=" Re:",fmusage[]=FM_USAGE; static const struct {const char*hedr;int lnr;}cdigest[]= { #define X(name,value) bsl(name), #include "header.h" /* pull in the precalculated references */ #undef X }; /* * sender determination fields in order of importance/reliability * reply-address determination fields (wrepl specifies the weight * for header replies and wrrepl specifies the weight for header * replies where Resent- header are used, while the position in the * table index specifies the weight for envelope replies and From_ * line creation. * * I bet this is the first time you've seen a bar graph in * C-source-code :-) */ static const struct {const char*head;int len,wrepl,wrrepl;}sest[]= { sslbar(replyto ,"*********" ,"********" ), sslbar(Fromm ,"**foo***" ,"**bar**" ), sslbar(sender ,"*******" ,"******" ), sslbar(res_replyto ,"*" ,"***********" ), sslbar(res_from ,"*" ,"**********" ), sslbar(res_sender ,"*" ,"*********" ), sslbar(path ,"**" ,"*" ), sslbar(retreceiptto ,"***" ,"**" ), sslbar(errorsto ,"****" ,"***" ), sslbar(returnpath ,"******" ,"*****" ), sslbar(From_ ,"*****" ,"****" ), }; static struct saved rex[]= { bsl(subject),bsl(references),bsl(messageid),bsl(date) }; #define subj (rex+0) #define refr (rex+1) #define msid (rex+2) #define hdate (rex+3) #ifdef sMAILBOX_SEPARATOR #define emboxsep smboxsep #define MAILBOX_SEPARATOR static const char smboxsep[]=sMAILBOX_SEPARATOR; #endif /* sMAILBOX_SEPARATOR */ #ifdef eMAILBOX_SEPARATOR #ifdef emboxsep #undef emboxsep #else #define MAILBOX_SEPARATOR #endif static const char emboxsep[]=eMAILBOX_SEPARATOR; #endif /* eMAILBOX_SEPARATOR */ const char binsh[]=BinSh,sfolder[]=FOLDER, couldntw[]="Couldn't write to stdout",formailn[]=FORMAILN; int errout,oldstdout,quiet=1,zap,buflast,lenfileno; long initfileno; char ffileno[LEN_FILENO_VAR+8*sizeof(initfileno)*4/10+1+1]=DEFfileno; int lexitcode; /* dummy, for waitfor() */ pid_t child= -1; int childlimit; unsigned long rhash; FILE*mystdout; int nrskip,nrtotal= -1,retval=EXIT_SUCCESS; size_t buflen,buffilled; long Totallen; char*buf,*logsummary; struct field*rdheader,*xheader,*Xheader,*uheader,*Uheader; static struct field*iheader,*Iheader,*aheader,*Aheader,*Rheader,*nheader; static int areply; static void logfolder P((void)) /* estimate the no. of characters needed to */ { size_t i;charNUM(num,Totallen); /* represent Totallen */ static const char tabchar[]=TABCHAR; if(logsummary) { putssn(sfolder,STRLEN(sfolder));putssn(logsummary,i=strlen(logsummary)); i+=STRLEN(sfolder);i-=i%TABWIDTH; do putssn(tabchar,STRLEN(tabchar)); while((i+=TABWIDTH)fld_text)[p->Tot_len-1]='\0'; if(eqFrom_(chp)) /* continued From_ to */ for(;chp=strstr(chp,"\n>");*++chp=' '); /* continued regular field */ if(newl==STRLEN(From_)&&eqFrom_(newname)) { for(chp=p->fld_text;chp=strchr(chp,'\n');) /* continued regular */ if(*++chp==' '||*chp=='\t') /* to continued From_ field */ *chp='>'; for(chp=p->fld_text;chp=strstr(chp,"\n ");*++chp='>'); goto replaceall; } if(newname[newl-1]==HEAD_DELIMITER) /* completely new field */ replaceall: oldl=p->id_len; /* replace the old one entirely */ p->id_len+=(int)newl-(int)oldl;p->fld_text[p->Tot_len-1]='\n'; p->Tot_len=(i=p->Tot_len-oldl)+newl; if(newl>oldl) *pointer=p=realloc(p,FLD_HEADSIZ+p->Tot_len); chp=p->fld_text;tmemmove(chp+newl,chp+oldl,i);tmemmove(chp,newname,newl); } static void procfields(sareply)const int sareply; { struct field*fldp,**afldp; fldp= *(afldp= &rdheader); while(fldp) { struct field*fp2; if(!sareply&& (fp2=findf(fldp,&iheader))&& !(areply&&fldp->id_len>=fp2->Tot_len-1)) /* filled replacement? */ { renfield(afldp,(size_t)0,old_,STRLEN(old_)); /* implicitly rename */ goto fixfldp; } if((fp2=findf(fldp,&Iheader))&& /* delete fields */ !(sareply&&fldp->id_lenTot_len-1)) /* empty replacement? */ goto delfld; if(fp2=findf(fldp,&Rheader)) /* explicitly rename field */ { renfield(afldp,fp2->id_len,(char*)fp2->fld_text+fp2->id_len, fp2->Tot_len-fp2->id_len); fixfldp: fldp= *afldp; } ;{ struct field*uf; if((uf=findf(fldp,&uheader))&&!uf->fld_ref) uf->fld_ref=afldp; /* first uheader, keep it */ else if(fp2=findf(fldp,&Uheader)) { if(fp2->fld_ref) { struct field**ch_afldp; if(afldp==(ch_afldp= &(*fp2->fld_ref)->fld_next)) afldp=fp2->fld_ref; /* deleting own reference */ for(fldp=Uheader;fldp;fldp=fldp->fld_next) if(fldp->fld_ref==ch_afldp) /* rearrange references to */ fldp->fld_ref=fp2->fld_ref; /* vanishing field */ delfield(fp2->fld_ref); /* delete old Uheader */ } fp2->fld_ref=afldp; /* keep last Uheader */ } else if(uf) /* delete all following uheaders */ delfld: { fldp=delfield(afldp); continue; } } fldp= *(afldp= &(*afldp)->fld_next); } } /* checks if the last field in rdheader looks like a known digest header */ static int digheadr P((void)) { char*chp;int i;size_t j;struct field*fp; for(fp=rdheader;fp->fld_next;fp=fp->fld_next); /* skip to the last */ i=maxindex(cdigest);chp=fp->fld_text;j=fp->id_len; while(chp[j-2]==' '||chp[j-2]=='\t') /* whitespace before the colon? */ j--; while((cdigest[i].lnr!=j||strncasecmp(cdigest[i].hedr,chp,j-1))&&i--); return i>=0||j>STRLEN(old_)&&!strncasecmp(old_,chp,STRLEN(old_))|| j>STRLEN(x_)&&!strncasecmp(x_,chp,STRLEN(x_)); } static int artheadr P((void)) /* could it be the start of an article? */ { if(!rdheader&&!strncmp(buf,Article_,STRLEN(Article_))) { addbuf();rdheader->id_len=STRLEN(Article_); return 1; } return 0; } /* lifted out of main() to reduce main()'s size */ static char*getsender(namep,fldp,headreply)char*namep;struct field*fldp; const int headreply; { char*chp;int i,nowm;size_t j;static int lastm; chp=fldp->fld_text;j=fldp->id_len;i=maxindex(sest); while((sest[i].len!=j||strncasecmp(sest[i].head,chp,j))&&i--); if(i>=0&&(i!=maxindex(sest)||fldp==rdheader)) /* found anything? */ { char*saddr;char*tmp; /* determine the weight */ nowm=areply&&headreply?headreply==1?sest[i].wrepl:sest[i].wrrepl:i;chp+=j; tmp=malloc(j=fldp->Tot_len-j);tmemmove(tmp,chp,j);(chp=tmp)[j-1]='\0'; if(sest[i].head==From_) { char*pastad; if(strchr(saddr=chp,'\n')) /* multiple From_ lines */ nowm-=2; /* aren't as trustworthy */ if(*saddr=='\n'&&(pastad=strchr(saddr,' '))) saddr=pastad+1; /* reposition at the address */ chp=saddr; while((pastad=strchr(chp,'\n'))&&(pastad=strchr(pastad,' '))) chp=pastad+1; /* skip to the last uucp >From */ if(pastad=strchr(chp,' ')) /* found an address? */ { char*savetmp; /* lift it out */ savetmp=malloc(1+(j=pastad-chp)+1+1);tmemmove(savetmp,chp,j); savetmp[j]='\0'; /* make work copy */ if(strchr(savetmp,'@')) /* domain attached? */ chp=savetmp,savetmp=tmp,tmp=chp; /* ok, ready */ else /* no domain, bang away! :-) */ { static const char remf[]=" remote from ",fwdb[]=" forwarded by "; char*p1,*p2; chp=tmp; for(;;) { int c; p1=strstr(saddr,remf); if(!(p2=strstr(saddr,fwdb))&&!p1) break; /* no more info */ if(!p1||p2&&p2lastm) /* better than previous ones */ goto pnewname; } else if(sest[i].head==returnpath) /* nill Return-Path: */ { saddr=(char*)mdaemon;nowm=maxindex(sest)+2; /* override */ pnewname: lastm=nowm;saddr=strcpy(malloc(strlen(saddr)+1),saddr); if(namep) free(namep); namep=saddr; } } free(tmp); } /* save headers for later perusal */ return namep; } /* lifted out of main() to reduce main()'s size */ static void elimdups(namep,idcache,maxlen,split)const char*const namep; FILE*idcache;const long maxlen;const int split; { int dupid=0;char*key,*oldnewl; key=(char*)namep; /* not to worry, no change will be noticed */ if(!areply) { key=0; if(msid->rexl) /* any Message-ID: ? */ *(oldnewl=(key=msid->rexp)+msid->rexl-1)='\0'; } /* wipe out trailing newline */ if(key) { long insoffs=maxlen; while(*key==' ') /* strip leading spaces */ key++; do { int j;char*p; /* start reading & comparing the next word */ for(p=key;(j=fgetc(idcache))==*p;p++) if(!j) /* end of word? */ { if(!quiet) nlog("Duplicate key found:"),elog(key),elog("\n"); dupid=1; goto dupfound; /* YES! duplicate found */ } if(!j) /* end of word? */ { if(p==key&&insoffs==maxlen) /* first character? */ { insoffs=ftell(idcache)-1; /* found end of */ goto skiprest; /* circular buffer */ } } else skiprest: for(;;) /* skip the rest of the word */ { switch(fgetc(idcache)) { case EOF: goto noluck; default: continue; case '\0':; } break; } } while(ftell(idcache)=maxlen) /* past our quota? */ insoffs=0; /* start up front again */ fseek(idcache,insoffs,SEEK_SET);fwrite(key,1,strlen(key)+1,idcache); putc('\0',idcache); /* mark new end of buffer */ dupfound: fseek(idcache,(long)0,SEEK_SET); /* rewind, for any next run */ if(!areply) *oldnewl='\n'; /* restore the newline */ } if(!split) /* not splitting? terminate early */ exit(dupid?EXIT_SUCCESS:1); if(dupid) /* duplicate? suppress output */ closemine(),opensink(); } static PROGID; int main(lastm,argv)int lastm;const char*const argv[]; { int i,split=0,force=0,bogus=1,every=0,headreply=0,digest=0,nowait=0,keepb=0, minfields=(char*)progid-(char*)progid,conctenate=0,babyl=0,babylstart, berkeley=0,forgetclen; long maxlen,ctlength;FILE*idcache=0;pid_t thepid; size_t j,lnl,escaplen;char*chp,*namep,*escap=ESCAP; struct field*fldp,*fp2,**afldp,*fdate,*fcntlength,*fsubject,*fFrom_; if(lastm) /* sanity check, any argument at all? */ #define Qnext_arg() if(!*chp&&!(chp=(char*)*++argv))goto usg while(chp=(char*)*++argv) { if((lastm= *chp++)==FM_SKIP) goto number; else if(lastm!=FM_TOTAL) goto usg; for(;;) { switch(lastm= *chp++) { case FM_TRUST:headreply|=1; continue; case FM_REPLY:areply=1; continue; case FM_FORCE:force=1; continue; case FM_EVERY:every=1; continue; case FM_BABYL:babyl=every=1; case FM_DIGEST:digest=1; continue; case FM_NOWAIT:nowait=1;Qnext_arg(); childlimit=strtol(chp,&chp,10); continue; case FM_KEEPB:keepb=1; continue; case FM_CONCATENATE:conctenate=1; continue; case FM_ZAPWHITE:zap=1; continue; case FM_QUIET:quiet=1; if(*chp=='-') chp++,quiet=0; continue; case FM_LOGSUMMARY:Qnext_arg(); if(strlen(logsummary=chp)>MAXfoldlen) chp[MAXfoldlen]='\0'; detab(chp); break; case FM_SPLIT:split=1; if(!*chp) { ++argv; goto parsedoptions; } goto usg; case HELPOPT1:case HELPOPT2:elog(fmusage);elog(FM_HELP); elog(FM_HELP2); /* had to split up FM_HELP, compiler limits */ goto xusg; case FM_DUPLICATE:case FM_MINFIELDS:Qnext_arg();chp++; default:chp--; number: if(*chp-'0'>(unsigned)9) /* the number is not >=0 */ goto usg; i=strtol(chp,&chp,10); switch(lastm) /* where does the number go? */ { case FM_SKIP:nrskip=i; break; case FM_DUPLICATE:maxlen=i;Qnext_arg(); if(!(idcache=fopen(chp,"r+b"))&& /* existing cache? */ !(idcache=fopen(chp,"w+b"))) /* create cache? */ { nlog("Couldn't open");logqnl(chp); return EX_CANTCREAT; } goto nextarg; case FM_MINFIELDS:minfields=i; break; default:nrtotal=i; } continue; case FM_BOGUS:bogus=0; continue; case FM_BERKELEY:berkeley=1; continue; case FM_QPREFIX:Qnext_arg();escap=chp; break; case FM_VERSION:elog(formailn);elog(VERSION); goto xusg; case FM_ADD_IFNOT:case FM_ADD_ALWAYS:case FM_REN_INSERT: case FM_DEL_INSERT:case FM_EXTRACT:case FM_EXTRC_KEEP: case FM_FIRST_UNIQ:case FM_LAST_UNIQ:case FM_ReNAME:Qnext_arg(); i=breakfield(chp,lnl=strlen(chp)); switch(lastm) { case FM_ADD_IFNOT: if(i>0) break; if(i!=-STRLEN(Resent_)||-i!=lnl|| /* the only partial */ strncasecmp(chp,Resent_,STRLEN(Resent_)+1)) /* field */ goto invfield; /* allowed with -a is Resent- */ headreply|=2; goto nextarg; /* don't add to the list */ default: if(-i!=lnl) /* it is not an early ending field */ case FM_ADD_ALWAYS: if(i<=0) /* and it is not a valid field */ goto invfield; /* complain */ case FM_ReNAME:; /* everything allowed */ } chp[lnl]='\n'; /* terminate the line */ afldp=addfield(lastm==FM_REN_INSERT?&iheader: lastm==FM_DEL_INSERT?&Iheader:lastm==FM_ADD_IFNOT?&aheader: lastm==FM_ADD_ALWAYS?&Aheader:lastm==FM_EXTRACT?&xheader: lastm==FM_FIRST_UNIQ?&uheader:lastm==FM_LAST_UNIQ?&Uheader: lastm==FM_EXTRC_KEEP?&Xheader:&Rheader,chp,++lnl); if(lastm==FM_ReNAME) /* then we need a second field */ { int copied=0; for(namep=(chp=(fldp= *afldp)->fld_text)+lnl, chp+=lnl=fldp->id_len;chp0) /* complete first field */ goto invfield; /* impossible combination */ else i= -i; if(i) tmemmove((char*)fldp->fld_text+lnl,chp,i),copied=1; else if(namep>chp|| /* garbage? */ !(chp=(char*)*++argv)|| /* look at next arg */ (!(i=breakfield(chp,strlen(chp)))&& /* fieldish? */ *chp)|| /* but "" is fine */ i<=0&&(i= -i,lastm>0)) /* impossible combination */ invfield: { nlog("Invalid field-name:");logqnl(chp?chp:""); goto usg; } *afldp=fldp= realloc(fldp,FLD_HEADSIZ+(fldp->Tot_len=lnl+i)); if(!copied) /* if not squeezed on yet */ tmemmove((char*)fldp->fld_text+lnl,chp,i); /* do now */ } case '\0':; } break; } nextarg:; } parsedoptions: escaplen=strlen(escap);mystdout=stdout;signal(SIGPIPE,SIG_IGN); #ifdef SIGCHLD signal(SIGCHLD,SIG_DFL); #endif thepid=getpid(); if(babyl) /* skip BABYL leader */ { while(getchar()!=BABYL_SEP1||getchar()!=BABYL_SEP2||getchar()!='\n') while(getchar()!='\n'); while(getchar()!='\n'); } while((buflast=getchar())=='\n'); /* skip leading garbage */ if(split) { char**ep;char**vfileno=0; if(buflast==EOF) /* avoid splitting empty messages */ return EXIT_SUCCESS; for(ep=environ;*ep;ep++) /* gobble through the environment */ if(!strncmp(*ep,ffileno,LEN_FILENO_VAR)) /* look for FILENO= */ vfileno=ep; /* yes, found it */ if(!vfileno) /* FILENO= found in the environment? */ { size_t envlen; /* no, pity */ envlen=(ep-environ+1)*sizeof*environ; /* current length */ tmemmove(ep=malloc(envlen+sizeof*environ),environ,envlen); *(vfileno=(char**)((char*)(environ=ep)+envlen))=0;*--vfileno=ffileno; } /* copy over the array */ if((lenfileno=strlen(chp= *vfileno+LEN_FILENO_VAR))> STRLEN(ffileno)-LEN_FILENO_VAR-1) /* check the desired width */ lenfileno=STRLEN(ffileno)-LEN_FILENO_VAR-1; /* too big, truncate */ if((initfileno=strtol(chp,&chp,10))<0) /* fetch the initial value */ lenfileno--; /* correct it for negatives */ if(*chp) /* no valid number? */ lenfileno= -1; /* disable the FILENO generation */ else *vfileno=ffileno; /* stuff our template in the environment */ oldstdout=dup(STDOUT);fclose(stdout); if(!nrtotal) goto onlyhead; startprog((const char*Const*)argv); if(!minfields) /* no user specified minimum? */ minfields=DEFminfields; /* take our default */ } else if(nrskip>0||nrtotal>=0||every||digest||minfields||nowait) goto usg; /* only valid in combination with split */ if((xheader||Xheader)&&logsummary||keepb&&!(areply||xheader||Xheader)) usg: /* options sanity check */ { elog(fmusage); /* impossible mix */ xusg: return EX_USAGE; } if(headreply==2) /* -aResent- is only allowed */ { chp=(char*)Resent_; /* as a modifier to header replies */ goto invfield; } buf=malloc(buflen=Bsize);Totallen=0;i=maxindex(rex); /* prime some buffers */ do rex[i].rexp=malloc(1); while(i--); fdate=0;addfield(&fdate,date,STRLEN(date)); /* fdate is only for searching */ fcntlength=0;addfield(&fcntlength,cntlength,STRLEN(cntlength)); /* ditto */ fFrom_=0;addfield(&fFrom_,From_,STRLEN(From_)); fsubject=0;addfield(&fsubject,subject,STRLEN(subject)); /* likewise */ forgetclen=digest|| /* forget Content-Length: for a digest */ berkeley|| /* for Berkeley format */ keepb&& /* if we're keeping the body and */ (areply|| /* autoreplying */ Xheader&& /* or eXtracting without */ !findf(fcntlength,&Xheader)); /* getting Content-Length: */ if(areply) /* when auto-replying */ addfield(&iheader,xloop,STRLEN(xloop)); /* preserve X-Loop: fields */ if(!readhead()) /* start looking */ { #ifdef sMAILBOX_SEPARATOR /* check for a leading */ if(!strncmp(smboxsep,buf,STRLEN(smboxsep))) /* mailbox separator */ { buffilled=0; /* skip it */ goto startover; } #endif if(digest&&artheadr()) goto startover; } else startover: while(readhead()); /* read in the whole header */ cleanheader(); ;{ size_t lenparkedbuf;void*parkedbuf;int wasafrom_; if(rdheader) { char*tmp,*tmp2; if(!strncmp(tmp=(char*)rdheader->fld_text,Article_,STRLEN(Article_))) tmp[STRLEN(Article_)-1]=HEAD_DELIMITER; else if(babyl&& !force&& !strncmp(tmp,mailfrom,STRLEN(mailfrom))&& eqFrom_(tmp2=skpspace(tmp+STRLEN(mailfrom)))) { rdheader->id_len=STRLEN(From_); tmemmove(tmp,tmp2,rdheader->Tot_len-=tmp2-tmp); } } namep=0;Totallen=0;i=maxindex(rex); do rex[i].rexl=0; while(i--); /* reset all state information */ clear_uhead(uheader);clear_uhead(Uheader); wasafrom_=!force&&rdheader&&eqFrom_(rdheader->fld_text); procfields(areply); for(fldp= *(afldp= &rdheader);fldp;) { if(zap) /* go through the linked list of header-fields */ { chp=fldp->fld_text+(j=fldp->id_len); if(chp[-1]==HEAD_DELIMITER) if((*chp!=' '&&*chp!='\t')&&fldp->Tot_len>j+1) { chp=j+(*afldp=fldp= realloc(fldp,FLD_HEADSIZ+(i=fldp->Tot_len++)+1))->fld_text; tmemmove(chp+1,chp,i-j);*chp=' '; } else if(fldp->Tot_len<=j+2) { *afldp=fldp->fld_next;free(fldp);fldp= *afldp; continue; } } if(conctenate) concatenate(fldp); /* save fields for later perusal */ namep=getsender(namep,fldp,headreply); i=maxindex(rex);chp=fldp->fld_text;j=fldp->id_len; while((rex[i].lenr!=j||strncasecmp(rex[i].headr,chp,j))&&i--); chp+=j; if(i>=0&&(j=fldp->Tot_len-j)>1) /* found anything? */ { tmemmove(rex[i].rexp=realloc(rex[i].rexp,(rex[i].rexl=j)+1),chp,j); rex[i].rexp[j]='\0'; /* add a terminating \0 */ } fldp= *(afldp= &fldp->fld_next); } if(idcache) elimdups(namep,idcache,maxlen,split); ctlength=0; if(!forgetclen&&(fldp=findf(fcntlength,&rdheader))) { *(chp=(char*)fldp->fld_text+fldp->Tot_len-1)='\0'; /* terminate it */ ctlength=strtol((char*)fldp->fld_text+STRLEN(cntlength),(char**)0,10); *chp='\n'; /* restore the trailing newline */ } tmemmove(parkedbuf=malloc(buffilled),buf,lenparkedbuf=buffilled); buffilled=0; /* moved the contents of buf out of the way temporarily */ if(areply) /* autoreply requested, we clean up the header */ { for(fldp= *(afldp= &rdheader);fldp;) if(!(fp2=findf(fldp,&iheader))||fp2->id_lenTot_len-1) *afldp=fldp->fld_next,free(fldp),fldp= *afldp; /* remove all */ else /* except the ones mentioned */ fldp= *(afldp= &fldp->fld_next); /* as -i ...: */ loadbuf(To,STRLEN(To));loadchar(' '); /* generate the To: field */ if(namep) /* did we find a valid return address at all? */ loadbuf(namep,strlen(namep)); /* then insert it here */ else /* or insert our default */ retval=EX_NOUSER,loadbuf(unknown,STRLEN(unknown)); loadchar('\n');addbuf(); /* add it to rdheader */ if(subj->rexl) /* any Subject: found? */ { loadbuf(subject,STRLEN(subject)); /* sure, check for leading */ if(strncasecmp(skpspace(chp=subj->rexp),Re,STRLEN(Re))) /* Re: */ loadbuf(re,STRLEN(re)); /* no Re: , add one ourselves */ loadsaved(subj);addbuf(); } if(refr->rexl||msid->rexl) /* any References: or Message-ID: */ { loadbuf(references,STRLEN(references)); /* yes insert References: */ if(refr->rexl) { if(msid->rexl) /* if we're going to append a Message-ID */ --refr->rexl; /* suppress the trailing newline */ loadsaved(refr); } if(msid->rexl) loadsaved(msid); /* here's our missing newline */ addbuf(); } if(msid->rexl) /* do we add an In-Reply-To: field? */ loadbuf(inreplyto,STRLEN(inreplyto)),loadsaved(msid),addbuf(); procfields(0); } else if(!force&& /* are we allowed to add From_ lines? */ (!rdheader||!eqFrom_(rdheader->fld_text))&& /* is it missing? */ ((fldp=findf(fFrom_,&aheader))&&STRLEN(From_)+1>=fldp->Tot_len|| !wasafrom_&& /* if there was no From_ */ !findf(fFrom_,&iheader)&& /* and From_ is not being */ !findf(fFrom_,&Iheader)&& /* supressed */ !findf(fFrom_,&Rheader))) { struct field*old;time_t t; /* insert a From_ line up front */ t=time((time_t*)0);old=rdheader;rdheader=0; loadbuf(From_,STRLEN(From_)); if(namep) /* we found a valid return address */ loadbuf(namep,strlen(namep)); else loadbuf(unknown,STRLEN(unknown)); loadchar(' '); /* insert one extra blank */ if(!hdate->rexl||!findf(fdate,&aheader)) /* Date: */ loadchar(' '),chp=ctime(&t),loadbuf(chp,strlen(chp)); /* no Date: */ else /* we generate it ourselves */ loadsaved(hdate); /* yes, found Date:, then copy from it */ addbuf();rdheader->fld_next=old; } for(fldp=aheader;fldp;fldp=fldp->fld_next) if(!findf(fldp,&rdheader)) /* only add what didn't exist */ if(fldp->id_len+1>=fldp->Tot_len&& /* field name only */ (fldp->id_len==STRLEN(messageid)&& !strncasecmp(fldp->fld_text,messageid,STRLEN(messageid))|| fldp->id_len==STRLEN(res_messageid)&& !strncasecmp(fldp->fld_text,res_messageid,STRLEN(res_messageid)) )) { char*p;const char*name;unsigned long h1,h2,h3; static unsigned long h4; /* conjure up a `unique' msg-id field */ h1=time((time_t*)0);h2=thepid;h3=rhash; p=chp=malloc(fldp->id_len+2+((sizeof h1*8+5)/6+1)*4+ strlen(name=hostname())+2); /* allocate worst case length */ memcpy(p,fldp->fld_text,fldp->id_len);*(p+=fldp->id_len)=' '; *++p='<';*(p=ultoan(h3,p+1))='.';*(p=ultoan(h4,p+1))='.'; *(p=ultoan(h2,p+1))='.';*(p=ultoan(h1,p+1))='@';strcpy(p+1,name); *(p=strchr(p,'\0'))='>';*++p='\n';addfield(&nheader,chp,p-chp+1); free(chp);h4++; /* put it in */ } else addfield(&nheader,fldp->fld_text,fldp->Tot_len); if(logsummary) { if(eqFrom_(rdheader->fld_text)) putssn(rdheader->fld_text,rdheader->Tot_len); if(fldp=findf(fsubject,&rdheader)) { concatenate(fldp);(chp=fldp->fld_text)[i=fldp->Tot_len-1]='\0'; detab(chp);putcs(' '); putssn(chp,i>=MAXSUBJECTSHOW?MAXSUBJECTSHOW:i);putcs('\n'); } } /* restore the saved contents of buf */ tmemmove(buf,parkedbuf,buffilled=lenparkedbuf);free(parkedbuf); } flushfield(&rdheader);flushfield(&nheader);dispfield(Aheader); dispfield(iheader);dispfield(Iheader); if(namep) free(namep); if(keepb||!(xheader||Xheader)) /* we're not just extracting fields */ lputcs('\n'); /* make sure it is followed by an empty line */ if(!keepb&&(areply||xheader||Xheader)) /* decision time */ { logfolder(); /* we throw away the rest */ if(split) closemine(); else /* terminate early, only the header was needed */ goto onlyhead; opensink(); /* discard the body */ } lnl=1; /* last line was a newline */ if(buffilled==1) /* the header really ended with a newline */ buffilled=0; /* throw it away, since we already inserted it */ if(babyl) { int c,lc; /* ditch pseudo BABYL header */ for(lc=0;c=getchar(),c!=EOF&&(c!='\n'||lc!='\n');lc=c); buflast=c;babylstart=0; } if(ctlength>0) { if(buffilled) lputssn(buf,buffilled),ctlength-=buffilled,buffilled=lnl=0; ;{ int tbl=buflast,lwr='\n'; while(--ctlength>=0&&tbl!=EOF) /* skip Content-Length: bytes */ lnl=lwr==tbl&&lwr=='\n',putcs(lwr=tbl),tbl=getchar(); if((buflast=tbl)=='\n'&&lwr!=tbl) /* just before a line break? */ putcs('\n'),buflast=getchar(); /* wrap up loose end */ } if(!quiet&&ctlength>0) { charNUM(num,ctlength); nlog(cntlength);elog(" field exceeds actual length by "); ultstr(0,(unsigned long)ctlength,num);elog(num);elog(" bytes\n"); } } while(buffilled||!lnl||buflast!=EOF) /* continue the quest, line by line */ { if(!buffilled) /* is it really empty? */ readhead(); /* read the next field */ if(!babyl||babylstart) /* don't split BABYL files everywhere */ { if(rdheader) /* anything looking like a header found? */ { if(eqFrom_(chp=rdheader->fld_text)) /* check if it's From_ */ fromanyway: { register size_t k; if(split&& (lnl||every)&& /* more thorough check for a postmark */ (k=strcspn(chp=skpspace(chp+STRLEN(From_))," \t\n"))&& *skpspace(chp+k)!='\n') goto accuhdr; /* ok, postmark found, split it */ if(bogus) /* disarm */ lputssn(escap,escaplen); } else if(split&&digest&&(lnl||every)&&digheadr()) /* digest? */ accuhdr: { for(i=minfields;--i&&readhead()&&digheadr();); /* found enough */ if(!i) /* then split it! */ splitit: { if(!lnl) /* did the previous mail end with an empty line? */ lputcs('\n'); /* but now it does :-) */ logfolder(); if(fclose(mystdout)==EOF||errout==EOF) { split= -1; if(!quiet) nlog(couldntw),elog(", continuing...\n"); } if(!nowait&&*argv) /* wait till the child has finished */ { int excode; if((excode=waitfor(child))!=EXIT_SUCCESS&& retval==EXIT_SUCCESS) retval=excode; } if(!nrtotal) goto nconlyhead; startprog((const char*Const*)argv); goto startover; } /* and there we go again */ } } else if(eqFrom_(buf)) /* special case, From_ line */ { addbuf(); /* add it manually, readhead() didn't */ goto fromanyway; } else if(split&&digest&&(lnl||every)&&artheadr()) goto accuhdr; } #ifdef MAILBOX_SEPARATOR if(!strncmp(emboxsep,buf,STRLEN(emboxsep))) /* end of mail? */ { if(split) /* gobble up the next start separator */ { buffilled=0; #ifdef sMAILBOX_SEPARATOR getline();buffilled=0; /* but only if it's defined */ #endif if(buflast!=EOF) /* if any */ goto splitit; break; } #ifdef eMAILBOX_SEPARATOR if(buflast==EOF) break; #endif if(bogus) goto putsp; /* escape it with a space */ } else if(!strncmp(smboxsep,buf,STRLEN(smboxsep))&&bogus) putsp: lputcs(' '); #endif /* MAILBOX_SEPARATOR */ lnl=buffilled==1; /* check if we just read an empty line */ if(babyl&&*buf==BABYL_SEP1) babylstart=1,closemine(),opensink(); /* discard the rest */ if(areply&&bogus) /* escape the body */ if(fldp=rdheader) /* we already read some "valid" fields */ { register char*p; rdheader=0; do /* careful, they can contain newlines */ { fp2=fldp->fld_next;chp=fldp->fld_text; do { lputssn(escap,escaplen); if(p=memchr(chp,'\n',fldp->Tot_len)) p++; else p=(char*)fldp->fld_text+fldp->Tot_len; lputssn(chp,p-chp); } while((chp=p)<(char*)fldp->fld_text+fldp->Tot_len); free(fldp); /* delete it */ } while(fldp=fp2); /* escape all fields we found */ } else { if(buffilled>1) /* we don't escape empty lines, looks neat */ lputssn(escap,escaplen); goto flbuf; } else if(rdheader) { struct field*ox,*oX; ox=xheader;oX=Xheader;xheader=Xheader=0;flushfield(&rdheader); xheader=ox;Xheader=oX; /* beware, after this buf can still be filled */ } else flbuf: lputssn(buf,buffilled),buffilled=0; } /* make sure the mail ends with an empty line */ logfolder(); onlyhead: closemine(); nconlyhead: if(split) /* wait for everyone */ { int excode; close(STDIN); /* close stdin now, we're not reading anymore */ while((excode=waitfor((pid_t)0))!=NO_PROCESS) if(retval==EXIT_SUCCESS&&excode!=EXIT_SUCCESS) retval=excode; } if(retval<0) retval=EX_UNAVAILABLE; return retval!=EXIT_SUCCESS?retval:split<0?EX_IOERR:EXIT_SUCCESS; } int eqFrom_(a)const char*const a; { return !strncmp(a,From_,STRLEN(From_)); } int breakfield(line,len)const char*const line;size_t len; /* look where the */ { const char*p=line; /* fieldname ends (RFC 822 specs) */ while(len) { switch(*p) { default:len--; if(iscntrl(*p)) /* no control characters allowed */ break; p++; continue; case HEAD_DELIMITER: len=p-line; return len?len+1:0; /* eureka! */ case ' ':case '\t': /* whitespace is okay right before the colon */ if(p>line) /* but only if we've seen something else already */ { const char*q=++p; while(--len&&(*q==' '||*q=='\t')) /* skip forward */ q++; if(len&&*q==HEAD_DELIMITER) /* it's okay */ return q-line+1; if(eqFrom_(line)) /* special case, From_ */ return STRLEN(From_); } /* it was bogus after all */ } break; } return -(int)(p-line); /* sorry, does not seem to be a legitimate field */ }