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