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
12 static /*const*/char rcsid[]=
13  "$Id: formail.c,v 1.102 2001/08/04 07:07:43 guenther Exp $";
14 #endif
15 static /*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 
32 static 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 
44 static 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  */
62 static 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 
76 static 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
87 static 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
95 static const char emboxsep[]=eMAILBOX_SEPARATOR;
96 #endif /* eMAILBOX_SEPARATOR */
97 
98 const char binsh[]=BinSh,sfolder[]=FOLDER,
99  couldntw[]="Couldn't write to stdout",formailn[]=FORMAILN;
100 int errout,oldstdout,quiet=1,zap,buflast,lenfileno;
101 long initfileno;
102 char ffileno[LEN_FILENO_VAR+8*sizeof(initfileno)*4/10+1+1]=DEFfileno;
103 int lexitcode;					     /* dummy, for waitfor() */
104 pid_t child= -1;
105 int childlimit;
106 unsigned long rhash;
107 FILE*mystdout;
108 int nrskip,nrtotal= -1,retval=EXIT_SUCCESS;
109 size_t buflen,buffilled;
110 long Totallen;
111 char*buf,*logsummary;
112 struct field*rdheader,*xheader,*Xheader,*uheader,*Uheader;
113 static struct field*iheader,*Iheader,*aheader,*Aheader,*Rheader,*nheader;
114 static int areply;
115 
logfolder(void)116 static 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 
renfield(pointer,oldl,newname,newl)128 static 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 */
142 replaceall:
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 
procfields(sareply)151 static 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);
168 fixfldp:
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 */
187 delfld:	 { 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 */
digheadr(void)195 static 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 
artheadr(void)206 static 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 */
getsender(namep,fldp,headreply)214 static 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 */
295 pnewname:  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 */
elimdups(namep,idcache,maxlen,split)306 static 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
335 skiprest:  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? */
347 noluck:
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 */
352 dupfound:
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 
363 static PROGID;
364 
main(lastm,argv)365 int 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--;
421 number:		 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 */
496 invfield:	     { 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 	 }
508 nextarg:;
509       }
510 parsedoptions:
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))
554 usg:						     /* options sanity check */
555    { elog(fmusage);					   /* impossible mix */
556 xusg:
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
590 startover:
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_ */
777 fromanyway: { 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? */
787 accuhdr:    { for(i=minfields;--i&&readhead()&&digheadr();); /* found enough */
788 	      if(!i)					   /* then split it! */
789 splitit:       { 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 	   get_line();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)
836 putsp:	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
871 flbuf:	lputssn(buf,buffilled),buffilled=0;
872    }			       /* make sure the mail ends with an empty line */
873   logfolder();
874 onlyhead:
875   closemine();
876 nconlyhead:
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 
eqFrom_(a)889 int eqFrom_(a)const char*const a;
890 { return !strncmp(a,From_,STRLEN(From_));
891 }
892 
breakfield(line,len)893 int 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