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