1/************************************************************************
2 *	formail - The mail (re)formatter				*
3 *									*
4 *	Seems to be relatively bug free.				*
5 *									*
6 *	Copyright (c) 1990-2000, S.R. van den Berg, The Netherlands	*
7 *	Copyright (c) 1999-2001, Philip Guenther, The United States	*
8 *							of America	*
9 *	#include "../README"						*
10 ************************************************************************/
11#ifdef RCS
12static /*const*/char rcsid[]=
13 "$Id: formail.c,v 1.102 2001/08/04 07:07:43 guenther Exp $";
14#endif
15static /*const*/char rcsdate[]="$Date: 2001/08/04 07:07:43 $";
16#include "includes.h"
17#include <ctype.h>		/* iscntrl() */
18#include "formail.h"
19#include "acommon.h"
20#include "sublib.h"
21#include "shell.h"
22#include "common.h"
23#include "fields.h"
24#include "ecommon.h"
25#include "formisc.h"
26#include "../patchlevel.h"
27
28#define ssl(str)		str,STRLEN(str)
29#define bsl(str)		{ssl(str)}
30#define sslbar(str,bar1,bar2)	{ssl(str),STRLEN(bar1)-1,STRLEN(bar2)-1}
31
32static const char
33#define X(name,value)	name[]=value,
34#include "header.h"				  /* pull in the definitions */
35#undef X
36 From_[]=		FROM,				/* VNIX 'From ' line */
37 Article_[]=		"Article ",		   /* USENET 'Article ' line */
38 x_[]=			"X-",				/* general extension */
39 old_[]=		OLD_PREFIX,			     /* my extension */
40 xloop[]=		"X-Loop:",				/* ditto ... */
41 Resent_[]=		"Resent-",	   /* for tweaking reply preferences */
42 mdaemon[]="<>",unknown[]=UNKNOWN,re[]=" Re:",fmusage[]=FM_USAGE;
43
44static const struct {const char*hedr;int lnr;}cdigest[]=
45{
46#define X(name,value)	bsl(name),
47#include "header.h"		     /* pull in the precalculated references */
48#undef X
49};
50
51/*
52 *	sender determination fields in order of importance/reliability
53 *	reply-address determination fields (wrepl specifies the weight
54 *	for header replies and wrrepl specifies the weight for header
55 *	replies where Resent- header are used, while the position in the
56 *	table index specifies the weight for envelope replies and From_
57 *	line creation.
58 *
59 *	I bet this is the first time you've seen a bar graph in
60 *	C-source-code :-)
61 */
62static const struct {const char*head;int len,wrepl,wrrepl;}sest[]=
63{ sslbar(replyto	,"*********"	,"********"	),
64  sslbar(Fromm		,"**foo***"	,"**bar**"	),
65  sslbar(sender		,"*******"	,"******"	),
66  sslbar(res_replyto	,"*"		,"***********"	),
67  sslbar(res_from	,"*"		,"**********"	),
68  sslbar(res_sender	,"*"		,"*********"	),
69  sslbar(path		,"**"		,"*"		),
70  sslbar(retreceiptto	,"***"		,"**"		),
71  sslbar(errorsto	,"****"		,"***"		),
72  sslbar(returnpath	,"******"	,"*****"	),
73  sslbar(From_		,"*****"	,"****"		),
74};
75
76static struct saved rex[]=
77{ bsl(subject),bsl(references),bsl(messageid),bsl(date)
78};
79#define subj	(rex+0)
80#define refr	(rex+1)
81#define msid	(rex+2)
82#define hdate	(rex+3)
83
84#ifdef sMAILBOX_SEPARATOR
85#define emboxsep	smboxsep
86#define MAILBOX_SEPARATOR
87static const char smboxsep[]=sMAILBOX_SEPARATOR;
88#endif /* sMAILBOX_SEPARATOR */
89#ifdef eMAILBOX_SEPARATOR
90#ifdef emboxsep
91#undef emboxsep
92#else
93#define MAILBOX_SEPARATOR
94#endif
95static const char emboxsep[]=eMAILBOX_SEPARATOR;
96#endif /* eMAILBOX_SEPARATOR */
97
98const char binsh[]=BinSh,sfolder[]=FOLDER,
99 couldntw[]="Couldn't write to stdout",formailn[]=FORMAILN;
100int errout,oldstdout,quiet=1,zap,buflast,lenfileno;
101long initfileno;
102char ffileno[LEN_FILENO_VAR+8*sizeof(initfileno)*4/10+1+1]=DEFfileno;
103int lexitcode;					     /* dummy, for waitfor() */
104pid_t child= -1;
105int childlimit;
106unsigned long rhash;
107FILE*mystdout;
108int nrskip,nrtotal= -1,retval=EXIT_SUCCESS;
109size_t buflen,buffilled;
110long Totallen;
111char*buf,*logsummary;
112struct field*rdheader,*xheader,*Xheader,*uheader,*Uheader;
113static struct field*iheader,*Iheader,*aheader,*Aheader,*Rheader,*nheader;
114static int areply;
115
116static void logfolder P((void))	 /* estimate the no. of characters needed to */
117{ size_t i;charNUM(num,Totallen);		       /* represent Totallen */
118  static const char tabchar[]=TABCHAR;
119  if(logsummary)
120   { putssn(sfolder,STRLEN(sfolder));putssn(logsummary,i=strlen(logsummary));
121     i+=STRLEN(sfolder);i-=i%TABWIDTH;
122     do putssn(tabchar,STRLEN(tabchar));
123     while((i+=TABWIDTH)<LENoffset);
124     ultstr(7,Totallen,num);putssn(num,strlen(num));putcs('\n');
125   }
126}
127
128static void renfield(pointer,oldl,newname,newl)struct field**const pointer;
129 size_t oldl;const size_t newl;const char*const newname;    /* rename fields */
130{ struct field*p;size_t i;char*chp;
131  p= *pointer;(chp=p->fld_text)[p->Tot_len-1]='\0';
132  if(eqFrom_(chp))				       /* continued From_ to */
133     for(;chp=strstr(chp,"\n>");*++chp=' ');	  /* continued regular field */
134  if(newl==STRLEN(From_)&&eqFrom_(newname))
135   { for(chp=p->fld_text;chp=strchr(chp,'\n');)		/* continued regular */
136	if(*++chp==' '||*chp=='\t')		 /* to continued From_ field */
137	   *chp='>';
138     for(chp=p->fld_text;chp=strstr(chp,"\n ");*++chp='>');
139     goto replaceall;
140   }
141  if(newname[newl-1]==HEAD_DELIMITER)		     /* completely new field */
142replaceall:
143     oldl=p->id_len;			     /* replace the old one entirely */
144  p->id_len+=(int)newl-(int)oldl;p->fld_text[p->Tot_len-1]='\n';
145  p->Tot_len=(i=p->Tot_len-oldl)+newl;
146  if(newl>oldl)
147     *pointer=p=realloc(p,FLD_HEADSIZ+p->Tot_len);
148  chp=p->fld_text;tmemmove(chp+newl,chp+oldl,i);tmemmove(chp,newname,newl);
149}
150
151static void procfields(sareply)const int sareply;
152{ struct field*fldp,**afldp;
153  fldp= *(afldp= &rdheader);
154  while(fldp)
155   { struct field*fp2;
156     if(!sareply&&
157	(fp2=findf(fldp,&iheader))&&
158	!(areply&&fldp->id_len>=fp2->Tot_len-1))      /* filled replacement? */
159      { renfield(afldp,(size_t)0,old_,STRLEN(old_));	/* implicitly rename */
160	goto fixfldp;
161      }
162     if((fp2=findf(fldp,&Iheader))&&			    /* delete fields */
163	!(sareply&&fldp->id_len<fp2->Tot_len-1))       /* empty replacement? */
164	goto delfld;
165     if(fp2=findf(fldp,&Rheader))		  /* explicitly rename field */
166      { renfield(afldp,fp2->id_len,(char*)fp2->fld_text+fp2->id_len,
167	 fp2->Tot_len-fp2->id_len);
168fixfldp:
169	fldp= *afldp;
170      }
171     ;{ struct field*uf;
172	if((uf=findf(fldp,&uheader))&&!uf->fld_ref)
173	   uf->fld_ref=afldp;			   /* first uheader, keep it */
174	else if(fp2=findf(fldp,&Uheader))
175	 { if(fp2->fld_ref)
176	    { struct field**ch_afldp;
177	      if(afldp==(ch_afldp= &(*fp2->fld_ref)->fld_next))
178		 afldp=fp2->fld_ref;		   /* deleting own reference */
179	      for(fldp=Uheader;fldp;fldp=fldp->fld_next)
180		 if(fldp->fld_ref==ch_afldp)	  /* rearrange references to */
181		    fldp->fld_ref=fp2->fld_ref;		  /* vanishing field */
182	      delfield(fp2->fld_ref);		       /* delete old Uheader */
183	    }
184	   fp2->fld_ref=afldp;				/* keep last Uheader */
185	 }
186	else if(uf)			    /* delete all following uheaders */
187delfld:	 { fldp=delfield(afldp);
188	   continue;
189	 }
190      }
191     fldp= *(afldp= &(*afldp)->fld_next);
192   }
193}
194    /* checks if the last field in rdheader looks like a known digest header */
195static int digheadr P((void))
196{ char*chp;int i;size_t j;struct field*fp;
197  for(fp=rdheader;fp->fld_next;fp=fp->fld_next);	 /* skip to the last */
198  i=maxindex(cdigest);chp=fp->fld_text;j=fp->id_len;
199  while(chp[j-2]==' '||chp[j-2]=='\t')	     /* whitespace before the colon? */
200     j--;
201  while((cdigest[i].lnr!=j||strncasecmp(cdigest[i].hedr,chp,j-1))&&i--);
202  return i>=0||j>STRLEN(old_)&&!strncasecmp(old_,chp,STRLEN(old_))||
203   j>STRLEN(x_)&&!strncasecmp(x_,chp,STRLEN(x_));
204}
205
206static int artheadr P((void))	     /* could it be the start of an article? */
207{ if(!rdheader&&!strncmp(buf,Article_,STRLEN(Article_)))
208   { addbuf();rdheader->id_len=STRLEN(Article_);
209     return 1;
210   }
211  return 0;
212}
213			     /* lifted out of main() to reduce main()'s size */
214static char*getsender(namep,fldp,headreply)char*namep;struct field*fldp;
215 const int headreply;
216{ char*chp;int i,nowm;size_t j;static int lastm;
217  chp=fldp->fld_text;j=fldp->id_len;i=maxindex(sest);
218  while((sest[i].len!=j||strncasecmp(sest[i].head,chp,j))&&i--);
219  if(i>=0&&(i!=maxindex(sest)||fldp==rdheader))		  /* found anything? */
220   { char*saddr;char*tmp;			     /* determine the weight */
221     nowm=areply&&headreply?headreply==1?sest[i].wrepl:sest[i].wrrepl:i;chp+=j;
222     tmp=malloc(j=fldp->Tot_len-j);tmemmove(tmp,chp,j);(chp=tmp)[j-1]='\0';
223     if(sest[i].head==From_)
224      { char*pastad;
225	if(strchr(saddr=chp,'\n'))		     /* multiple From_ lines */
226	   nowm-=2;				    /* aren't as trustworthy */
227	if(*saddr=='\n'&&(pastad=strchr(saddr,' ')))
228	   saddr=pastad+1;			/* reposition at the address */
229	chp=saddr;
230	while((pastad=strchr(chp,'\n'))&&(pastad=strchr(pastad,' ')))
231	   chp=pastad+1;		      /* skip to the last uucp >From */
232	if(pastad=strchr(chp,' '))			/* found an address? */
233	 { char*savetmp;				      /* lift it out */
234	   savetmp=malloc(1+(j=pastad-chp)+1+1);tmemmove(savetmp,chp,j);
235	   savetmp[j]='\0';				   /* make work copy */
236	   if(strchr(savetmp,'@'))			 /* domain attached? */
237	      chp=savetmp,savetmp=tmp,tmp=chp;			/* ok, ready */
238	   else					/* no domain, bang away! :-) */
239	    { static const char remf[]=" remote from ",fwdb[]=" forwarded by ";
240	      char*p1,*p2;
241	      chp=tmp;
242	      for(;;)
243	       { int c;
244		 p1=strstr(saddr,remf);
245		 if(!(p2=strstr(saddr,fwdb))&&!p1)
246		    break;				     /* no more info */
247		 if(!p1||p2&&p2<p1)		      /* pick the first bang */
248		    p1=p2+STRLEN(fwdb);
249		 else
250		    p1+=STRLEN(remf);
251		 for(;;)				     /* copy it over */
252		  { switch(c= *p1++)
253		     { default:*chp++=c;
254			  continue;
255		       case '\0':case '\n':*chp++='!';	     /* for the buck */
256		     }
257		    break;
258		  }
259		 saddr=p1;				/* continue the hunt */
260	       }
261	      strcpy(chp,savetmp);chp=tmp;	     /* attach the user part */
262	    }			  /* (temporary buffers might have switched) */
263	   free(savetmp);savetmp=strchr(tmp,'\0');	      /* prepend '<' */
264	   tmemmove(tmp+1,tmp,savetmp-tmp);*tmp='<';savetmp[1]='\0';
265	 }
266      }
267     while(*(chp=skpspace(chp))=='\n')
268	chp++;
269     for(saddr=0;;chp=skipwords(chp))			/* skip RFC 822 wise */
270      { switch(*chp)
271	 { default:
272	      if(!saddr)		   /* if we haven't got anything yet */
273		 saddr=chp;			/* this might be the address */
274	      continue;
275	   case '<':skipwords(saddr=chp);	  /* hurray, machine useable */
276	   case '\0':;
277	 }
278	break;
279      }
280     if(saddr)				    /* any useful mailaddress found? */
281      { if(*saddr)				  /* did it have any length? */
282	 { if(!strpbrk(saddr,"@!/"))
283	      nowm-=(maxindex(sest)+2)*4;		/* depreciate "user" */
284	   else if(strstr(saddr,".UUCP"))
285	      nowm-=(maxindex(sest)+2)*3;	 /* depreciate .UUCP address */
286	   else if(strchr(saddr,'@')&&!strchr(saddr,'.'))
287	      nowm-=(maxindex(sest)+2)*2;	     /* depreciate user@host */
288	   else if(strchr(saddr,'!'))
289	      nowm-=(maxindex(sest)+2)*1;	     /* depreciate bangpaths */
290	   if(!namep||nowm>lastm)		/* better than previous ones */
291	      goto pnewname;
292	 }
293	else if(sest[i].head==returnpath)		/* nill Return-Path: */
294	 { saddr=(char*)mdaemon;nowm=maxindex(sest)+2;		 /* override */
295pnewname:  lastm=nowm;saddr=strcpy(malloc(strlen(saddr)+1),saddr);
296	   if(namep)
297	      free(namep);
298	   namep=saddr;
299	 }
300      }
301     free(tmp);
302   }					   /* save headers for later perusal */
303  return namep;
304}
305			     /* lifted out of main() to reduce main()'s size */
306static void elimdups(namep,idcache,maxlen,split)const char*const namep;
307 FILE*idcache;const long maxlen;const int split;
308{ int dupid=0;char*key,*oldnewl;
309  key=(char*)namep;		  /* not to worry, no change will be noticed */
310  if(!areply)
311   { key=0;
312     if(msid->rexl)					/* any Message-ID: ? */
313	*(oldnewl=(key=msid->rexp)+msid->rexl-1)='\0';
314   }						/* wipe out trailing newline */
315  if(key)
316   { long insoffs=maxlen;
317     while(*key==' ')				     /* strip leading spaces */
318	key++;
319     do
320      { int j;char*p;		  /* start reading & comparing the next word */
321	for(p=key;(j=fgetc(idcache))==*p;p++)
322	   if(!j)					     /* end of word? */
323	    { if(!quiet)
324		 nlog("Duplicate key found:"),elog(key),elog("\n");
325	      dupid=1;
326	      goto dupfound;			     /* YES! duplicate found */
327	    }
328	if(!j)						     /* end of word? */
329	 { if(p==key&&insoffs==maxlen)			 /* first character? */
330	    { insoffs=ftell(idcache)-1;			     /* found end of */
331	      goto skiprest;				  /* circular buffer */
332	    }
333	 }
334	else
335skiprest:  for(;;)				/* skip the rest of the word */
336	    { switch(fgetc(idcache))
337	       { case EOF:
338		    goto noluck;
339		 default:
340		    continue;
341		 case '\0':;
342	       }
343	      break;
344	    }
345      }
346     while(ftell(idcache)<maxlen);			  /* past our quota? */
347noluck:
348     if(insoffs>=maxlen)				  /* past our quota? */
349	insoffs=0;				     /* start up front again */
350     fseek(idcache,insoffs,SEEK_SET);fwrite(key,1,strlen(key)+1,idcache);
351     putc('\0',idcache);			   /* mark new end of buffer */
352dupfound:
353     fseek(idcache,(long)0,SEEK_SET);		 /* rewind, for any next run */
354     if(!areply)
355	*oldnewl='\n';				      /* restore the newline */
356   }
357  if(!split)				  /* not splitting?  terminate early */
358     exit(dupid?EXIT_SUCCESS:1);
359  if(dupid)				       /* duplicate? suppress output */
360     closemine(),opensink();
361}
362
363static PROGID;
364
365int main(lastm,argv)int lastm;const char*const argv[];
366{ int i,split=0,force=0,bogus=1,every=0,headreply=0,digest=0,nowait=0,keepb=0,
367   minfields=(char*)progid-(char*)progid,conctenate=0,babyl=0,babylstart,
368   berkeley=0,forgetclen;
369  long maxlen,ctlength;FILE*idcache=0;pid_t thepid;
370  size_t j,lnl,escaplen;char*chp,*namep,*escap=ESCAP;
371  struct field*fldp,*fp2,**afldp,*fdate,*fcntlength,*fsubject,*fFrom_;
372  if(lastm)			       /* sanity check, any argument at all? */
373#define Qnext_arg()	if(!*chp&&!(chp=(char*)*++argv))goto usg
374     while(chp=(char*)*++argv)
375      { if((lastm= *chp++)==FM_SKIP)
376	   goto number;
377	else if(lastm!=FM_TOTAL)
378	   goto usg;
379	for(;;)
380	 { switch(lastm= *chp++)
381	    { case FM_TRUST:headreply|=1;
382		 continue;
383	      case FM_REPLY:areply=1;
384		 continue;
385	      case FM_FORCE:force=1;
386		 continue;
387	      case FM_EVERY:every=1;
388		 continue;
389	      case FM_BABYL:babyl=every=1;
390	      case FM_DIGEST:digest=1;
391		 continue;
392	      case FM_NOWAIT:nowait=1;Qnext_arg();
393		 childlimit=strtol(chp,&chp,10);
394		 continue;
395	      case FM_KEEPB:keepb=1;
396		 continue;
397	      case FM_CONCATENATE:conctenate=1;
398		 continue;
399	      case FM_ZAPWHITE:zap=1;
400		 continue;
401	      case FM_QUIET:quiet=1;
402		 if(*chp=='-')
403		    chp++,quiet=0;
404		 continue;
405	      case FM_LOGSUMMARY:Qnext_arg();
406		 if(strlen(logsummary=chp)>MAXfoldlen)
407		    chp[MAXfoldlen]='\0';
408		 detab(chp);
409		 break;
410	      case FM_SPLIT:split=1;
411		 if(!*chp)
412		  { ++argv;
413		    goto parsedoptions;
414		  }
415		 goto usg;
416	      case HELPOPT1:case HELPOPT2:elog(fmusage);elog(FM_HELP);
417		 elog(FM_HELP2); /* had to split up FM_HELP, compiler limits */
418		 goto xusg;
419	      case FM_DUPLICATE:case FM_MINFIELDS:Qnext_arg();chp++;
420	      default:chp--;
421number:		 if(*chp-'0'>(unsigned)9)	    /* the number is not >=0 */
422		    goto usg;
423		 i=strtol(chp,&chp,10);
424		 switch(lastm)			/* where does the number go? */
425		  { case FM_SKIP:nrskip=i;
426		       break;
427		    case FM_DUPLICATE:maxlen=i;Qnext_arg();
428		       if(!(idcache=fopen(chp,"r+b"))&&	  /* existing cache? */
429			  !(idcache=fopen(chp,"w+b")))	    /* create cache? */
430			{ nlog("Couldn't open");logqnl(chp);
431			  return EX_CANTCREAT;
432			}
433		       goto nextarg;
434		    case FM_MINFIELDS:minfields=i;
435		       break;
436		    default:nrtotal=i;
437		  }
438		 continue;
439	      case FM_BOGUS:bogus=0;
440		 continue;
441	      case FM_BERKELEY:berkeley=1;
442		 continue;
443	      case FM_QPREFIX:Qnext_arg();escap=chp;
444		 break;
445	      case FM_VERSION:elog(formailn);elog(VERSION);
446		 goto xusg;
447	      case FM_ADD_IFNOT:case FM_ADD_ALWAYS:case FM_REN_INSERT:
448	      case FM_DEL_INSERT:case FM_EXTRACT:case FM_EXTRC_KEEP:
449	      case FM_FIRST_UNIQ:case FM_LAST_UNIQ:case FM_ReNAME:Qnext_arg();
450		 i=breakfield(chp,lnl=strlen(chp));
451		 switch(lastm)
452		  { case FM_ADD_IFNOT:
453		       if(i>0)
454			  break;
455		       if(i!=-STRLEN(Resent_)||-i!=lnl|| /* the only partial */
456			strncasecmp(chp,Resent_,STRLEN(Resent_)+1)) /* field */
457			  goto invfield;       /* allowed with -a is Resent- */
458		       headreply|=2;
459		       goto nextarg;		    /* don't add to the list */
460		    default:
461		       if(-i!=lnl)	  /* it is not an early ending field */
462		    case FM_ADD_ALWAYS:
463			  if(i<=0)	      /* and it is not a valid field */
464			     goto invfield;			 /* complain */
465		    case FM_ReNAME:;		       /* everything allowed */
466		  }
467		 chp[lnl]='\n';			       /* terminate the line */
468		 afldp=addfield(lastm==FM_REN_INSERT?&iheader:
469		  lastm==FM_DEL_INSERT?&Iheader:lastm==FM_ADD_IFNOT?&aheader:
470		  lastm==FM_ADD_ALWAYS?&Aheader:lastm==FM_EXTRACT?&xheader:
471		  lastm==FM_FIRST_UNIQ?&uheader:lastm==FM_LAST_UNIQ?&Uheader:
472		  lastm==FM_EXTRC_KEEP?&Xheader:&Rheader,chp,++lnl);
473		 if(lastm==FM_ReNAME)	      /* then we need a second field */
474		  { int copied=0;
475		    for(namep=(chp=(fldp= *afldp)->fld_text)+lnl,
476		     chp+=lnl=fldp->id_len;chp<namep;++chp)
477		     { switch(*chp)			  /* skip whitespace */
478			{ case ' ':case '\t':case '\n':
479			     continue;
480			}
481		       break;
482		     }				   /* second field attached? */
483		    lastm=i;
484		    if((i=breakfield(chp,(size_t)(namep-chp)))<0) /* partial */
485		       if(lastm>0)		     /* complete first field */
486			  goto invfield;	   /* impossible combination */
487		       else
488			  i= -i;
489		    if(i)
490		       tmemmove((char*)fldp->fld_text+lnl,chp,i),copied=1;
491		    else if(namep>chp||				 /* garbage? */
492			    !(chp=(char*)*++argv)||	 /* look at next arg */
493			    (!(i=breakfield(chp,strlen(chp)))&& /* fieldish? */
494			     *chp)||			   /* but "" is fine */
495			    i<=0&&(i= -i,lastm>0)) /* impossible combination */
496invfield:	     { nlog("Invalid field-name:");logqnl(chp?chp:"");
497		       goto usg;
498		     }
499		    *afldp=fldp=
500		     realloc(fldp,FLD_HEADSIZ+(fldp->Tot_len=lnl+i));
501		    if(!copied)			   /* if not squeezed on yet */
502		       tmemmove((char*)fldp->fld_text+lnl,chp,i);  /* do now */
503		  }
504	      case '\0':;
505	    }
506	   break;
507	 }
508nextarg:;
509      }
510parsedoptions:
511  escaplen=strlen(escap);mystdout=stdout;signal(SIGPIPE,SIG_IGN);
512#ifdef SIGCHLD
513  signal(SIGCHLD,SIG_DFL);
514#endif
515  thepid=getpid();
516  if(babyl)						/* skip BABYL leader */
517   { while(getchar()!=BABYL_SEP1||getchar()!=BABYL_SEP2||getchar()!='\n')
518	while(getchar()!='\n');
519     while(getchar()!='\n');
520   }
521  while((buflast=getchar())=='\n');		     /* skip leading garbage */
522  if(split)
523   { char**ep;char**vfileno=0;
524     if(buflast==EOF)			   /* avoid splitting empty messages */
525	return EXIT_SUCCESS;
526     for(ep=environ;*ep;ep++)		   /* gobble through the environment */
527	if(!strncmp(*ep,ffileno,LEN_FILENO_VAR))	 /* look for FILENO= */
528	   vfileno=ep;					    /* yes, found it */
529     if(!vfileno)			/* FILENO= found in the environment? */
530      { size_t envlen;						 /* no, pity */
531	envlen=(ep-environ+1)*sizeof*environ;		   /* current length */
532	tmemmove(ep=malloc(envlen+sizeof*environ),environ,envlen);
533	*(vfileno=(char**)((char*)(environ=ep)+envlen))=0;*--vfileno=ffileno;
534      }						      /* copy over the array */
535     if((lenfileno=strlen(chp= *vfileno+LEN_FILENO_VAR))>
536	STRLEN(ffileno)-LEN_FILENO_VAR-1)	  /* check the desired width */
537	lenfileno=STRLEN(ffileno)-LEN_FILENO_VAR-1;	/* too big, truncate */
538     if((initfileno=strtol(chp,&chp,10))<0)	  /* fetch the initial value */
539	lenfileno--;				 /* correct it for negatives */
540     if(*chp)						 /* no valid number? */
541	lenfileno= -1;			    /* disable the FILENO generation */
542     else
543	*vfileno=ffileno;	    /* stuff our template in the environment */
544     oldstdout=dup(STDOUT);fclose(stdout);
545     if(!nrtotal)
546	goto onlyhead;
547     startprog((const char*Const*)argv);
548     if(!minfields)			       /* no user specified minimum? */
549	minfields=DEFminfields;				 /* take our default */
550   }
551  else if(nrskip>0||nrtotal>=0||every||digest||minfields||nowait)
552     goto usg;			     /* only valid in combination with split */
553  if((xheader||Xheader)&&logsummary||keepb&&!(areply||xheader||Xheader))
554usg:						     /* options sanity check */
555   { elog(fmusage);					   /* impossible mix */
556xusg:
557     return EX_USAGE;
558   }
559  if(headreply==2)				/* -aResent- is only allowed */
560   { chp=(char*)Resent_;		  /* as a modifier to header replies */
561     goto invfield;
562   }
563  buf=malloc(buflen=Bsize);Totallen=0;i=maxindex(rex); /* prime some buffers */
564  do rex[i].rexp=malloc(1);
565  while(i--);
566  fdate=0;addfield(&fdate,date,STRLEN(date)); /* fdate is only for searching */
567  fcntlength=0;addfield(&fcntlength,cntlength,STRLEN(cntlength));   /* ditto */
568  fFrom_=0;addfield(&fFrom_,From_,STRLEN(From_));
569  fsubject=0;addfield(&fsubject,subject,STRLEN(subject));	 /* likewise */
570  forgetclen=digest||		      /* forget Content-Length: for a digest */
571	     berkeley||				      /* for Berkeley format */
572	     keepb&&			    /* if we're keeping the body and */
573	      (areply||					     /* autoreplying */
574	       Xheader&&			    /* or eXtracting without */
575	       !findf(fcntlength,&Xheader));	  /* getting Content-Length: */
576  if(areply)					       /* when auto-replying */
577     addfield(&iheader,xloop,STRLEN(xloop));	  /* preserve X-Loop: fields */
578  if(!readhead())					    /* start looking */
579   {
580#ifdef sMAILBOX_SEPARATOR			      /* check for a leading */
581     if(!strncmp(smboxsep,buf,STRLEN(smboxsep)))	/* mailbox separator */
582      { buffilled=0;						  /* skip it */
583	goto startover;
584      }
585#endif
586     if(digest&&artheadr())
587	goto startover;
588   }
589  else
590startover:
591     while(readhead());				 /* read in the whole header */
592  cleanheader();
593  ;{ size_t lenparkedbuf;void*parkedbuf;int wasafrom_;
594     if(rdheader)
595      { char*tmp,*tmp2;
596	if(!strncmp(tmp=(char*)rdheader->fld_text,Article_,STRLEN(Article_)))
597	   tmp[STRLEN(Article_)-1]=HEAD_DELIMITER;
598	else if(babyl&&
599		!force&&
600		!strncmp(tmp,mailfrom,STRLEN(mailfrom))&&
601		eqFrom_(tmp2=skpspace(tmp+STRLEN(mailfrom))))
602	 { rdheader->id_len=STRLEN(From_);
603	   tmemmove(tmp,tmp2,rdheader->Tot_len-=tmp2-tmp);
604	 }
605      }
606     namep=0;Totallen=0;i=maxindex(rex);
607     do rex[i].rexl=0;
608     while(i--);			      /* reset all state information */
609     clear_uhead(uheader);clear_uhead(Uheader);
610     wasafrom_=!force&&rdheader&&eqFrom_(rdheader->fld_text);
611     procfields(areply);
612     for(fldp= *(afldp= &rdheader);fldp;)
613      { if(zap)		      /* go through the linked list of header-fields */
614	 { chp=fldp->fld_text+(j=fldp->id_len);
615	   if(chp[-1]==HEAD_DELIMITER)
616	      if((*chp!=' '&&*chp!='\t')&&fldp->Tot_len>j+1)
617	       { chp=j+(*afldp=fldp=
618		  realloc(fldp,FLD_HEADSIZ+(i=fldp->Tot_len++)+1))->fld_text;
619		 tmemmove(chp+1,chp,i-j);*chp=' ';
620	       }
621	      else if(fldp->Tot_len<=j+2)
622	       { *afldp=fldp->fld_next;free(fldp);fldp= *afldp;
623		 continue;
624	       }
625	 }
626	if(conctenate)
627	   concatenate(fldp);		    /* save fields for later perusal */
628	namep=getsender(namep,fldp,headreply);
629	i=maxindex(rex);chp=fldp->fld_text;j=fldp->id_len;
630	while((rex[i].lenr!=j||strncasecmp(rex[i].headr,chp,j))&&i--);
631	chp+=j;
632	if(i>=0&&(j=fldp->Tot_len-j)>1)			  /* found anything? */
633	 { tmemmove(rex[i].rexp=realloc(rex[i].rexp,(rex[i].rexl=j)+1),chp,j);
634	   rex[i].rexp[j]='\0';			     /* add a terminating \0 */
635	 }
636	fldp= *(afldp= &fldp->fld_next);
637      }
638     if(idcache)
639	elimdups(namep,idcache,maxlen,split);
640     ctlength=0;
641     if(!forgetclen&&(fldp=findf(fcntlength,&rdheader)))
642      { *(chp=(char*)fldp->fld_text+fldp->Tot_len-1)='\0';   /* terminate it */
643	ctlength=strtol((char*)fldp->fld_text+STRLEN(cntlength),(char**)0,10);
644	*chp='\n';			     /* restore the trailing newline */
645      }
646     tmemmove(parkedbuf=malloc(buffilled),buf,lenparkedbuf=buffilled);
647     buffilled=0;    /* moved the contents of buf out of the way temporarily */
648     if(areply)		      /* autoreply requested, we clean up the header */
649      { for(fldp= *(afldp= &rdheader);fldp;)
650	   if(!(fp2=findf(fldp,&iheader))||fp2->id_len<fp2->Tot_len-1)
651	      *afldp=fldp->fld_next,free(fldp),fldp= *afldp;   /* remove all */
652	   else					/* except the ones mentioned */
653	      fldp= *(afldp= &fldp->fld_next);		       /* as -i ...: */
654	loadbuf(To,STRLEN(To));loadchar(' ');	   /* generate the To: field */
655	if(namep)	       /* did we find a valid return address at all? */
656	   loadbuf(namep,strlen(namep));	      /* then insert it here */
657	else					    /* or insert our default */
658	   retval=EX_NOUSER,loadbuf(unknown,STRLEN(unknown));
659	loadchar('\n');addbuf();		       /* add it to rdheader */
660	if(subj->rexl)				      /* any Subject: found? */
661	 { loadbuf(subject,STRLEN(subject));	  /* sure, check for leading */
662	   if(strncasecmp(skpspace(chp=subj->rexp),Re,STRLEN(Re)))    /* Re: */
663	      loadbuf(re,STRLEN(re));	       /* no Re: , add one ourselves */
664	   loadsaved(subj);addbuf();
665	 }
666	if(refr->rexl||msid->rexl)	   /* any References: or Message-ID: */
667	 { loadbuf(references,STRLEN(references)); /* yes insert References: */
668	   if(refr->rexl)
669	    { if(msid->rexl)	    /* if we're going to append a Message-ID */
670		 --refr->rexl;		    /* suppress the trailing newline */
671	      loadsaved(refr);
672	    }
673	   if(msid->rexl)
674	      loadsaved(msid);		       /* here's our missing newline */
675	   addbuf();
676	 }
677	if(msid->rexl)			 /* do we add an In-Reply-To: field? */
678	   loadbuf(inreplyto,STRLEN(inreplyto)),loadsaved(msid),addbuf();
679	procfields(0);
680      }
681     else if(!force&&		       /* are we allowed to add From_ lines? */
682	     (!rdheader||!eqFrom_(rdheader->fld_text))&&   /* is it missing? */
683	     ((fldp=findf(fFrom_,&aheader))&&STRLEN(From_)+1>=fldp->Tot_len||
684	      !wasafrom_&&			    /* if there was no From_ */
685	      !findf(fFrom_,&iheader)&&		   /* and From_ is not being */
686	      !findf(fFrom_,&Iheader)&&				/* supressed */
687	      !findf(fFrom_,&Rheader)))
688      { struct field*old;time_t t;	     /* insert a From_ line up front */
689	t=time((time_t*)0);old=rdheader;rdheader=0;
690	loadbuf(From_,STRLEN(From_));
691	if(namep)			  /* we found a valid return address */
692	   loadbuf(namep,strlen(namep));
693	else
694	   loadbuf(unknown,STRLEN(unknown));
695	loadchar(' ');				   /* insert one extra blank */
696	if(!hdate->rexl||!findf(fdate,&aheader))		    /* Date: */
697	   loadchar(' '),chp=ctime(&t),loadbuf(chp,strlen(chp)); /* no Date: */
698	else					 /* we generate it ourselves */
699	   loadsaved(hdate);	      /* yes, found Date:, then copy from it */
700	addbuf();rdheader->fld_next=old;
701      }
702     for(fldp=aheader;fldp;fldp=fldp->fld_next)
703	if(!findf(fldp,&rdheader))	       /* only add what didn't exist */
704	   if(fldp->id_len+1>=fldp->Tot_len&&		  /* field name only */
705	      (fldp->id_len==STRLEN(messageid)&&
706	       !strncasecmp(fldp->fld_text,messageid,STRLEN(messageid))||
707	       fldp->id_len==STRLEN(res_messageid)&&
708	       !strncasecmp(fldp->fld_text,res_messageid,STRLEN(res_messageid))
709	      ))
710	    { char*p;const char*name;unsigned long h1,h2,h3;
711	      static unsigned long h4; /* conjure up a `unique' msg-id field */
712	      h1=time((time_t*)0);h2=thepid;h3=rhash;
713	      p=chp=malloc(fldp->id_len+2+((sizeof h1*8+5)/6+1)*4+
714	       strlen(name=hostname())+2);     /* allocate worst case length */
715	      memcpy(p,fldp->fld_text,fldp->id_len);*(p+=fldp->id_len)=' ';
716	      *++p='<';*(p=ultoan(h3,p+1))='.';*(p=ultoan(h4,p+1))='.';
717	      *(p=ultoan(h2,p+1))='.';*(p=ultoan(h1,p+1))='@';strcpy(p+1,name);
718	      *(p=strchr(p,'\0'))='>';*++p='\n';addfield(&nheader,chp,p-chp+1);
719	      free(chp);h4++;					/* put it in */
720	    }
721	   else
722	      addfield(&nheader,fldp->fld_text,fldp->Tot_len);
723     if(logsummary)
724      { if(eqFrom_(rdheader->fld_text))
725	   putssn(rdheader->fld_text,rdheader->Tot_len);
726	if(fldp=findf(fsubject,&rdheader))
727	 { concatenate(fldp);(chp=fldp->fld_text)[i=fldp->Tot_len-1]='\0';
728	   detab(chp);putcs(' ');
729	   putssn(chp,i>=MAXSUBJECTSHOW?MAXSUBJECTSHOW:i);putcs('\n');
730	 }
731      }					/* restore the saved contents of buf */
732     tmemmove(buf,parkedbuf,buffilled=lenparkedbuf);free(parkedbuf);
733   }
734  flushfield(&rdheader);flushfield(&nheader);dispfield(Aheader);
735  dispfield(iheader);dispfield(Iheader);
736  if(namep)
737     free(namep);
738  if(keepb||!(xheader||Xheader))	 /* we're not just extracting fields */
739     lputcs('\n');		/* make sure it is followed by an empty line */
740  if(!keepb&&(areply||xheader||Xheader))		    /* decision time */
741   { logfolder();				   /* we throw away the rest */
742     if(split)
743	closemine();
744     else		      /* terminate early, only the header was needed */
745	goto onlyhead;
746     opensink();					 /* discard the body */
747   }
748  lnl=1;					  /* last line was a newline */
749  if(buffilled==1)		   /* the header really ended with a newline */
750     buffilled=0;	      /* throw it away, since we already inserted it */
751  if(babyl)
752   { int c,lc;					/* ditch pseudo BABYL header */
753     for(lc=0;c=getchar(),c!=EOF&&(c!='\n'||lc!='\n');lc=c);
754     buflast=c;babylstart=0;
755   }
756  if(ctlength>0)
757   { if(buffilled)
758	lputssn(buf,buffilled),ctlength-=buffilled,buffilled=lnl=0;
759     ;{ int tbl=buflast,lwr='\n';
760	while(--ctlength>=0&&tbl!=EOF)	       /* skip Content-Length: bytes */
761	   lnl=lwr==tbl&&lwr=='\n',putcs(lwr=tbl),tbl=getchar();
762	if((buflast=tbl)=='\n'&&lwr!=tbl)	/* just before a line break? */
763	   putcs('\n'),buflast=getchar();		/* wrap up loose end */
764      }
765     if(!quiet&&ctlength>0)
766      { charNUM(num,ctlength);
767	nlog(cntlength);elog(" field exceeds actual length by ");
768	ultstr(0,(unsigned long)ctlength,num);elog(num);elog(" bytes\n");
769      }
770   }
771  while(buffilled||!lnl||buflast!=EOF)	 /* continue the quest, line by line */
772   { if(!buffilled)				      /* is it really empty? */
773	readhead();				      /* read the next field */
774     if(!babyl||babylstart)	       /* don't split BABYL files everywhere */
775      { if(rdheader)		    /* anything looking like a header found? */
776	 { if(eqFrom_(chp=rdheader->fld_text))	      /* check if it's From_ */
777fromanyway: { register size_t k;
778	      if(split&&
779		 (lnl||every)&&	       /* more thorough check for a postmark */
780		 (k=strcspn(chp=skpspace(chp+STRLEN(From_))," \t\n"))&&
781		 *skpspace(chp+k)!='\n')
782		 goto accuhdr;		     /* ok, postmark found, split it */
783	      if(bogus)						   /* disarm */
784		 lputssn(escap,escaplen);
785	    }
786	   else if(split&&digest&&(lnl||every)&&digheadr())	  /* digest? */
787accuhdr:    { for(i=minfields;--i&&readhead()&&digheadr();); /* found enough */
788	      if(!i)					   /* then split it! */
789splitit:       { if(!lnl)   /* did the previous mail end with an empty line? */
790		    lputcs('\n');		      /* but now it does :-) */
791		 logfolder();
792		 if(fclose(mystdout)==EOF||errout==EOF)
793		  { split= -1;
794		    if(!quiet)
795		       nlog(couldntw),elog(", continuing...\n");
796		  }
797		 if(!nowait&&*argv)	 /* wait till the child has finished */
798		  { int excode;
799		    if((excode=waitfor(child))!=EXIT_SUCCESS&&
800		       retval==EXIT_SUCCESS)
801		       retval=excode;
802		  }
803		 if(!nrtotal)
804		    goto nconlyhead;
805		 startprog((const char*Const*)argv);
806		 goto startover;
807	       }				    /* and there we go again */
808	    }
809	 }
810	else if(eqFrom_(buf))			 /* special case, From_ line */
811	 { addbuf();		       /* add it manually, readhead() didn't */
812	   goto fromanyway;
813	 }
814	else if(split&&digest&&(lnl||every)&&artheadr())
815	   goto accuhdr;
816      }
817#ifdef MAILBOX_SEPARATOR
818     if(!strncmp(emboxsep,buf,STRLEN(emboxsep)))	     /* end of mail? */
819      { if(split)		       /* gobble up the next start separator */
820	 { buffilled=0;
821#ifdef sMAILBOX_SEPARATOR
822	   pm_getline();buffilled=0;		 /* but only if it's defined */
823#endif
824	   if(buflast!=EOF)					   /* if any */
825	      goto splitit;
826	   break;
827	 }
828#ifdef eMAILBOX_SEPARATOR
829	if(buflast==EOF)
830	   break;
831#endif
832	if(bogus)
833	   goto putsp;				   /* escape it with a space */
834      }
835     else if(!strncmp(smboxsep,buf,STRLEN(smboxsep))&&bogus)
836putsp:	lputcs(' ');
837#endif /* MAILBOX_SEPARATOR */
838     lnl=buffilled==1;		      /* check if we just read an empty line */
839     if(babyl&&*buf==BABYL_SEP1)
840	babylstart=1,closemine(),opensink();		 /* discard the rest */
841     if(areply&&bogus)					  /* escape the body */
842	if(fldp=rdheader)	      /* we already read some "valid" fields */
843	 { register char*p;
844	   rdheader=0;
845	   do			       /* careful, they can contain newlines */
846	    { fp2=fldp->fld_next;chp=fldp->fld_text;
847	      do
848	       { lputssn(escap,escaplen);
849		 if(p=memchr(chp,'\n',fldp->Tot_len))
850		    p++;
851		 else
852		    p=(char*)fldp->fld_text+fldp->Tot_len;
853		 lputssn(chp,p-chp);
854	       }
855	      while((chp=p)<(char*)fldp->fld_text+fldp->Tot_len);
856	      free(fldp);					/* delete it */
857	    }
858	   while(fldp=fp2);		       /* escape all fields we found */
859	 }
860	else
861	 { if(buffilled>1)	  /* we don't escape empty lines, looks neat */
862	      lputssn(escap,escaplen);
863	   goto flbuf;
864	 }
865     else if(rdheader)
866      { struct field*ox,*oX;
867	ox=xheader;oX=Xheader;xheader=Xheader=0;flushfield(&rdheader);
868	xheader=ox;Xheader=oX; /* beware, after this buf can still be filled */
869      }
870     else
871flbuf:	lputssn(buf,buffilled),buffilled=0;
872   }			       /* make sure the mail ends with an empty line */
873  logfolder();
874onlyhead:
875  closemine();
876nconlyhead:
877  if(split)						/* wait for everyone */
878   { int excode;
879     close(STDIN);	       /* close stdin now, we're not reading anymore */
880     while((excode=waitfor((pid_t)0))!=NO_PROCESS)
881	if(retval==EXIT_SUCCESS&&excode!=EXIT_SUCCESS)
882	   retval=excode;
883   }
884  if(retval<0)
885     retval=EX_UNAVAILABLE;
886  return retval!=EXIT_SUCCESS?retval:split<0?EX_IOERR:EXIT_SUCCESS;
887}
888
889int eqFrom_(a)const char*const a;
890{ return !strncmp(a,From_,STRLEN(From_));
891}
892
893int breakfield(line,len)const char*const line;size_t len;  /* look where the */
894{ const char*p=line;			   /* fieldname ends (RFC 822 specs) */
895  while(len)
896   { switch(*p)
897      { default:len--;
898	   if(iscntrl(*p))		    /* no control characters allowed */
899	      break;
900	   p++;
901	   continue;
902	case HEAD_DELIMITER:
903	   len=p-line;
904	   return len?len+1:0;					  /* eureka! */
905	case ' ':case '\t':	/* whitespace is okay right before the colon */
906	   if(p>line)	    /* but only if we've seen something else already */
907	    { const char*q=++p;
908	      while(--len&&(*q==' '||*q=='\t'))		     /* skip forward */
909		 q++;
910	      if(len&&*q==HEAD_DELIMITER)			/* it's okay */
911		 return q-line+1;
912	      if(eqFrom_(line))			      /* special case, From_ */
913		 return STRLEN(From_);
914	    }					   /* it was bogus after all */
915      }
916     break;
917   }
918  return -(int)(p-line);    /* sorry, does not seem to be a legitimate field */
919}
920