1/************************************************************************ 2 * Routines that deal with the mailfolder(format) * 3 * * 4 * Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands * 5 * Copyright (c) 1999-2001, Philip Guenther, The United States * 6 * of America * 7 * #include "../README" * 8 ************************************************************************/ 9#ifdef RCS 10static /*const*/char rcsid[]= 11 "$Id: mailfold.c,v 1.104 2001/08/04 07:16:26 guenther Exp $"; 12#endif 13#include "procmail.h" 14#include "acommon.h" 15#include "sublib.h" 16#include "robust.h" 17#include "misc.h" 18#include "memblk.h" 19#include "pipes.h" 20#include "common.h" 21#include "exopen.h" 22#include "goodies.h" 23#include "variables.h" 24#include "locking.h" 25#include "lastdirsep.h" 26#include "foldinfo.h" 27#include "from.h" 28#include "shell.h" 29#include "mailfold.h" 30 31int logopened,rawnonl; 32off_t lasttell; 33static long lastdump; 34static volatile int mailread; /* if the mail is completely read in already */ 35static struct dyna_array confield; /* escapes, concatenations */ 36static const char*realstart,*restbody; 37static const char from_expr[]=FROM_EXPR; 38 39static const char*fifrom(fromw,lbound,ubound) 40 const char*fromw,*const lbound;char*const ubound; 41{ int i; /* terminate & scan block */ 42 i= *ubound;*ubound='\0';fromw=strstr(mx(fromw,lbound),from_expr);*ubound=i; 43 return fromw; 44} 45 46static int doesc; 47 /* inserts escape characters on outgoing mail */ 48static long getchunk(s,fromw,len)const int s;const char*fromw;const long len; 49{ static const char esc[]=ESCAP,*ffrom,*endp; 50 if(doesc) /* still something to escape since last time? */ 51 doesc=0,rwrite(s,esc,STRLEN(esc)),lastdump++; /* escape it */ 52 ffrom=0; /* start with a clean slate */ 53 if(fromw<thebody) /* are we writing the header now? */ 54 ffrom=fifrom(fromw,realstart,thebody); /* scan header */ 55 if(!ffrom&&(endp=fromw+len)>restbody) /* nothing yet? but in range? */ 56 { if((endp+=STRLEN(from_expr)-1)>(ffrom=themail.p+filled)) /* add slack */ 57 endp=(char*)ffrom; /* make sure we stay within bounds */ 58 ffrom=fifrom(fromw,restbody,endp); /* scan body block */ 59 } 60 return ffrom?(doesc=1,(ffrom-fromw)+1L):len; /* +1 to write out the '\n' */ 61} 62 63#ifdef sMAILBOX_SEPARATOR 64#define smboxseparator(fd) (ft_delim(type)&&\ 65 (part=len,rwrite(fd,sMAILBOX_SEPARATOR,STRLEN(sMAILBOX_SEPARATOR)))) 66#define MAILBOX_SEPARATOR 67#else 68#define smboxseparator(fd) 69#endif /* sMAILBOX_SEPARATOR */ 70#ifdef eMAILBOX_SEPARATOR 71#define emboxseparator(fd) \ 72 (ft_delim(type)&&rwrite(fd,eMAILBOX_SEPARATOR,STRLEN(eMAILBOX_SEPARATOR))) 73#ifndef MAILBOX_SEPARATOR 74#define MAILBOX_SEPARATOR 75#endif 76#else 77#define emboxseparator(fd) 78#endif /* eMAILBOX_SEPARATOR */ 79 80long dump(s,type,source,len)const int s,type;const char*source; 81 long len; 82{ int i;long part; 83 lasttell=i= -1;SETerrno(EBADF); 84 if(s>=0) 85 { if(ft_lock(type)&&(lseek(s,(off_t)0,SEEK_END),fdlock(s))) 86 nlog("Kernel-lock failed\n"); 87 lastdump=len;doesc=0; 88 if(ft_delim(type)&&!rawnonl) 89 part=getchunk(s,source,len); /* must escape From_ */ 90 else 91 part=len; 92 lasttell=lseek(s,(off_t)0,SEEK_END); 93 if(!rawnonl) 94 { smboxseparator(s); /* optional separator */ 95#ifndef NO_NFS_ATIME_HACK /* if it is a file, trick NFS into an */ 96 if(part&&ft_atime(type)) /* a_time<m_time */ 97 { struct stat stbuf; 98 rwrite(s,source++,1);len--;part--; /* set the trap */ 99 if(fstat(s,&stbuf)|| /* needed? */ 100 stbuf.st_mtime==stbuf.st_atime) 101 ssleep(1); /* ...what a difference this (tea) second makes... */ 102 } 103#endif 104 } 105 goto jin; 106 do 107 { part=getchunk(s,source,len); 108jin: while(part&&(i=rwrite(s,source,BLKSIZ<part?BLKSIZ:(int)part))) 109 { if(i<0) 110 goto writefin; 111 part-=i;len-=i;source+=i; 112 } 113 } 114 while(len); 115 if(!rawnonl) 116 { if(!len&&(lastdump<2||!(source[-1]=='\n'&&source[-2]=='\n'))&& 117 ft_forceblank(type)) 118 lastdump++,rwrite(s,newline,1); /* message always ends with a */ 119 emboxseparator(s); /* newline and an optional custom separator */ 120 } 121writefin: 122 i=type!=ft_PIPE&&fsync(s)&&errno!=EINVAL; /* EINVAL => wasn't a file */ 123 if(ft_lock(type)) 124 { int serrno=errno; /* save any error information */ 125 if(fdunlock()) 126 nlog("Kernel-unlock failed\n"); 127 SETerrno(serrno); 128 } 129 i=rclose(s)||i; 130 } /* return an error even if nothing was to be sent */ 131 return i&&!len?-1:len; 132} 133 134static int dirfile(chp,linkonly,type)char*const chp;const int linkonly,type; 135{ static const char lkingto[]="Linking to";struct stat stbuf; 136 if(type==ft_MH) 137 { long i=0; /* first let us try to prime i with the */ 138#ifndef NOopendir /* highest MH folder number we can find */ 139 long j;DIR*dirp;struct dirent*dp;char*chp2; 140 if(dirp=opendir(buf)) 141 { while(dp=readdir(dirp)) /* there still are directory entries */ 142 if((j=strtol(dp->d_name,&chp2,10))>i&&!*chp2) 143 i=j; /* yep, we found a higher number */ 144 closedir(dirp); /* aren't we neat today */ 145 } 146 else 147 readerr(buf); 148#endif /* NOopendir */ 149 if(chp-buf+sizeNUM(i)>linebuf) 150exlb: { nlog(exceededlb);setoverflow(); 151 goto ret; 152 } 153 ;{ int ok; 154 do ultstr(0,++i,chp); /* find first empty MH folder */ 155 while((ok=linkonly?rlink(buf2,buf,0):hlink(buf2,buf))&&errno==EEXIST); 156 if(linkonly) 157 { yell(lkingto,buf); 158 if(ok) 159 goto nolnk; 160 goto didlnk; 161 } 162 } 163 goto opn; 164 } 165 else if(type==ft_MAILDIR) 166 { if(!unique(buf,chp,linebuf,NORMperm,verbose,doMAILDIR)) 167 goto ret; 168 unlink(buf); /* found a name, remove file in tmp */ 169 memcpy(chp-MAILDIRLEN-1,maildirnew,MAILDIRLEN); /* but link directly */ 170 } /* into new */ 171 else /* ft_DIR */ 172 { size_t mpl=strlen(msgprefix); 173 if(chp-buf+mpl+sizeNUM(stbuf.st_ino)>linebuf) 174 goto exlb; 175 stat(buf2,&stbuf); /* filename with i-node number */ 176 ultoan((unsigned long)stbuf.st_ino,strcpy(chp,msgprefix)+mpl); 177 } 178 if(linkonly) 179 { yell(lkingto,buf); 180 if(rlink(buf2,buf,0)) /* hardlink the new file, it's a directory folder */ 181nolnk: nlog("Couldn't make link to"),logqnl(buf); 182 else 183didlnk: appendlastvar(buf); /* lastvar is "LASTFOLDER" here */ 184 goto ret; 185 } 186 if(!rlink(buf2,buf,0)) /* try rename-via-link */ 187opn: unlink(buf2); /* success; remove the original */ 188 else if(errno=EEXIST||!stat(buf,&stbuf)||errno!=ENOENT||rename(buf2,buf)) 189ret: return -1; /* rename it, but only if it won't replace an existing file */ 190 setlastfolder(buf); 191 return opena(buf); 192} 193 194int writefolder(boxname,linkfolder,source,len,ignwerr,dolock) 195 char*boxname,*linkfolder;const char*source;long len;const int ignwerr,dolock; 196{ char*chp,*chp2;mode_t mode;int fd,type; 197 if(*boxname=='|'&&(!linkfolder||linkfolder==Tmnate)) 198 { setlastfolder(boxname); 199 fd=rdup(Deliverymode==2?STDOUT:savstdout); 200 type=ft_PIPE; 201 goto dumpc; 202 } 203 if(boxname!=buf) 204 strcpy(buf,boxname); /* boxname can be found back in buf */ 205 if(linkfolder) /* any additional directories specified? */ 206 { size_t blen; 207 if(blen=Tmnate-linkfolder) /* copy the names into safety */ 208 Tmnate=(linkfolder=tmemmove(malloc(blen),linkfolder,blen))+blen; 209 else 210 linkfolder=0; 211 } 212 type=foldertype(0,0,&mode,0); /* the envelope please! */ 213 chp=strchr(buf,'\0'); 214 switch(type) 215 { case ft_FILE: 216 if(linkfolder) /* any leftovers? Now is the time to display them */ 217 concatenate(linkfolder),skipped(linkfolder),free(linkfolder); 218 if(!strcmp(devnull,buf)) 219 type=ft_PIPE,rawnonl=1; /* save the effort on /dev/null */ 220 else if(!(UPDATE_MASK&(mode|cumask))) 221 chmod(boxname,mode|UPDATE_MASK); 222 if(dolock&&type!=ft_PIPE) 223 { strcpy(chp,lockext); 224 if(!globlock||strcmp(buf,globlock)) 225 lockit(tstrdup(buf),&loclock); 226 *chp='\0'; 227 } 228 setlastfolder(boxname); 229 fd=opena(boxname); 230dumpc: if(dump(fd,type,source,len)&&!ignwerr) 231dumpf: { switch(errno) 232 { case ENOSPC:nlog("No space left to finish writing"),logqnl(buf); 233 break; 234#ifdef EDQUOT 235 case EDQUOT:nlog("Quota exceeded while writing"),logqnl(buf); 236 break; 237#endif 238 default:writeerr(buf); 239 } 240 if(lasttell>=0&&!truncate(boxname,lasttell)&&(logopened||verbose)) 241 nlog("Truncated file to former size\n"); /* undo garbage */ 242ret0: return 0; 243 } 244 return 1; 245 case ft_TOOLONG: 246exlb: nlog(exceededlb);setoverflow(); 247 case ft_CANTCREATE: 248retf: if(linkfolder) 249 free(linkfolder); 250 goto ret0; 251 case ft_MAILDIR: 252 if(source==themail.p) /* skip leading From_? */ 253 source=skipFrom_(source,&len); 254 strcpy(buf2,buf); 255 chp2=buf2+(chp-buf)-MAILDIRLEN; 256 *chp++= *MCDIRSEP_; 257 ;{ int retries=MAILDIRretries; 258 for(;;) 259 { struct stat stbuf; 260 if(0>(fd=unique(buf,chp,linebuf,NORMperm,verbose,doFD|doMAILDIR))) 261 goto nfail; 262 if(dump(fd,ft_MAILDIR,source,len)&&!ignwerr) 263 goto failed; 264 strcpy(chp2,maildirnew); 265 chp2+=MAILDIRLEN; 266 *chp2++= *MCDIRSEP_; 267 strcpy(chp2,chp); 268 if(!rlink(buf,buf2,0)) 269 { unlink(buf); 270 break; 271 } 272 else if(errno!=EEXIST&&lstat(buf2,&stbuf)&&errno==ENOENT&& 273 !rename(buf,buf2)) 274 break; 275 unlink(buf); 276 if(!retries--) 277 goto nfail; 278 } 279 } 280 setlastfolder(buf2); 281 break; 282 case ft_MH: 283#if 0 284 if(source==themail.p) 285 source=skipFrom_(source,&len); 286#endif 287 default: /* case ft_DIR: */ 288 *chp++= *MCDIRSEP_; 289 strcpy(buf2,buf); 290 chp2=buf2+(chp-buf); 291 if(!unique(buf2,chp2,linebuf,NORMperm,verbose,0)|| 292 0>(fd=dirfile(chp,0,type))) 293nfail: { nlog("Couldn't create or rename temp file");logqnl(buf); 294 goto retf; 295 } 296 if(dump(fd,type,source,len)&&!ignwerr) 297 { strcpy(buf,buf2); 298failed: unlink(buf);lasttell= -1; 299 if(linkfolder) 300 free(linkfolder); 301 goto dumpf; 302 } 303 strcpy(buf2,buf); 304 break; 305 } 306 if(!(UPDATE_MASK&(mode|cumask))) 307 { chp[-1]='\0'; /* restore folder name */ 308 chmod(buf,mode|UPDATE_MASK); 309 } 310 if(linkfolder) /* handle secondary folders */ 311 { for(boxname=linkfolder;boxname!=Tmnate;boxname=strchr(boxname,'\0')+1) 312 { strcpy(buf,boxname); 313 switch(type=foldertype(0,1,&mode,0)) 314 { case ft_TOOLONG:goto exlb; 315 case ft_CANTCREATE:continue; /* just skip it */ 316 case ft_DIR:case ft_MH:case ft_MAILDIR: 317 chp=strchr(buf,'\0'); 318 *chp= *MCDIRSEP_; 319 if(dirfile(chp+1,1,type)) /* link it with the original in buf2 */ 320 if(!(UPDATE_MASK&(mode|cumask))) 321 { *chp='\0'; 322 chmod(buf,mode|UPDATE_MASK); 323 } 324 break; 325 } 326 } 327 free(linkfolder); 328 } 329 return 1; 330} 331 332void logabstract(lstfolder)const char*const lstfolder; 333{ if(lgabstract>0||(logopened||verbose)&&lgabstract) /* don't mail it back? */ 334 { char*chp,*chp2;int i;static const char sfolder[]=FOLDER; 335 if(mailread) /* is the mail completely read in? */ 336 { i= *thebody;*thebody='\0'; /* terminate the header, just in case */ 337 if(eqFrom_(chp=themail.p)) /* any "From " header */ 338 { if(chp=strchr(themail.p,'\n')) 339 *chp='\0'; 340 else 341 chp=thebody; /* preserve mailbox format */ 342 elog(themail.p);elog(newline);*chp='\n'; /* (any length) */ 343 } 344 *thebody=i; /* eliminate the terminator again */ 345 if(!nextexit&& /* don't reenter malloc/free */ 346 (chp=egrepin(NSUBJECT,chp,(long)(thebody-chp),0))) 347 { size_t subjlen; 348 for(chp2= --chp;*--chp2!='\n';); 349 if((subjlen=chp-++chp2)>MAXSUBJECTSHOW) 350 subjlen=MAXSUBJECTSHOW; /* keep it within bounds */ 351 ((char*)tmemmove(buf,chp2,subjlen))[subjlen]='\0';detab(buf); 352 elog(" ");elog(buf);elog(newline); 353 } 354 } 355 elog(sfolder);strlcpy(buf,lstfolder,MAXfoldlen);detab(buf);elog(buf); 356 i=strlen(buf)+STRLEN(sfolder);i-=i%TABWIDTH; /* last dump */ 357 do elog(TABCHAR); 358 while((i+=TABWIDTH)<LENoffset); 359 ultstr(7,lastdump,buf);elog(buf);elog(newline); 360 } 361} 362 363static int concnd; /* last concatenation value */ 364 365void concon(ch)const int ch; /* flip between concatenated and split fields */ 366{ size_t i; 367 if(concnd!=ch) /* is this run redundant? */ 368 { concnd=ch; /* no, but note this one for next time */ 369 for(i=confield.filled;i;) /* step through the saved offsets */ 370 themail.p[acc_vall(confield,--i)]=ch; /* and flip every one */ 371 } 372} 373 374void readmail(rhead,tobesent)const long tobesent; 375{ char*chp,*pastend;static size_t contlengthoffset; 376 ;{ long dfilled; 377 if(rhead==2) /* already read, just examine what we have */ 378 dfilled=mailread=0; 379 else if(rhead) /* only read in a new header */ 380 { memblk new; 381 dfilled=mailread=0;makeblock(&new,0);readdyn(&new,&dfilled,0); 382 if(tobesent>dfilled&&isprivate) /* put it in place here */ 383 { tmemmove(themail.p+dfilled,thebody,filled-=tobesent); 384 tmemmove(themail.p,new.p,dfilled); 385 resizeblock(&themail,filled+=dfilled,1); 386 freeblock(&new); 387 } 388 else /* too big or must share -- switch blocks */ 389 { resizeblock(&new,filled-tobesent+dfilled,0); 390 tmemmove(new.p+dfilled,thebody,filled-=tobesent); 391 freeblock(&themail); 392 themail=new;private(1); 393 filled+=dfilled; 394 } 395 } 396 else 397 { if(!mailread||!filled) 398 rhead=1; /* yup, we read in a new header as well as new mail */ 399 mailread=0;dfilled=thebody-themail.p; 400 if(!isprivate) 401 { memblk new; 402 makeblock(&new,filled); 403 if(filled) 404 tmemmove(new.p,themail.p,filled); 405 freeblock(&themail); 406 themail=new;private(1); 407 } 408 readdyn(&themail,&filled,filled+tobesent); 409 } 410 pastend=filled+(thebody=themail.p); 411 while(thebody<pastend&&*thebody++=='\n'); /* skip leading garbage */ 412 realstart=thebody; 413 if(rhead) /* did we read in a new header anyway? */ 414 { confield.filled=0;concnd='\n'; 415 while(thebody=strchr(thebody,'\n')) 416 switch(*++thebody) /* mark continued fields */ 417 { case '\t':case ' ':app_vall(confield,(long)(thebody-1-themail.p)); 418 default: 419 continue; /* empty line marks end of header */ 420 case '\n':thebody++; 421 goto eofheader; 422 } 423 thebody=pastend; /* provide a default, in case there is no body */ 424eofheader: 425 contlengthoffset=0; /* traditional Berkeley format */ 426 if(!berkeley&& /* ignores Content-Length: */ 427 (chp=egrepin("^Content-Length:",themail.p, 428 (long)(thebody-themail.p),0))) 429 contlengthoffset=chp-themail.p; 430 } 431 else /* no new header read, keep it simple */ 432 thebody=themail.p+dfilled; /* that means we know where the body starts */ 433 } /* to make sure that the first From_ line is uninjured */ 434 if((chp=thebody)>themail.p) 435 chp--; 436 if(contlengthoffset) 437 { unsigned places;long cntlen,actcntlen;charNUM(num,cntlen); 438 chp=themail.p+contlengthoffset;cntlen=filled-(thebody-themail.p); 439 if(filled>1&&themail.p[filled-2]=='\n') /* no phantom '\n'? */ 440 cntlen--; /* make sure it points to the last '\n' */ 441 for(actcntlen=places=0;;) 442 { switch(*chp) 443 { default: /* fill'r up, please */ 444 if(places<=sizeof num-2) 445 *chp++='9',places++,actcntlen=(unsigned long)actcntlen*10+9; 446 else 447 *chp++=' '; /* ultra long Content-Length: field */ 448 continue; 449 case '\n':case '\0':; /* ok, end of the line */ 450 } 451 break; 452 } 453 if(cntlen<=0) /* any Content-Length at all? */ 454 cntlen=0; 455 ultstr(places,cntlen,num); /* our preferred size */ 456 if(!num[places]) /* does it fit in the existing space? */ 457 tmemmove(themail.p+contlengthoffset,num,places),actcntlen=cntlen; 458 chp=thebody+actcntlen; /* skip the actual no we specified */ 459 } 460 restbody=chp;mailread=1; 461} 462