/************************************************************************ * Routines that deal with the mailfolder(format) * * * * Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands * * Copyright (c) 1999-2001, Philip Guenther, The United States * * of America * * #include "../README" * ************************************************************************/ #ifdef RCS static /*const*/char rcsid[]= "$Id: mailfold.c,v 1.104 2001/08/04 07:16:26 guenther Exp $"; #endif #include "procmail.h" #include "acommon.h" #include "sublib.h" #include "robust.h" #include "misc.h" #include "memblk.h" #include "pipes.h" #include "common.h" #include "exopen.h" #include "goodies.h" #include "variables.h" #include "locking.h" #include "lastdirsep.h" #include "foldinfo.h" #include "from.h" #include "shell.h" #include "mailfold.h" int logopened,rawnonl; off_t lasttell; static long lastdump; static volatile int mailread; /* if the mail is completely read in already */ static struct dyna_array confield; /* escapes, concatenations */ static const char*realstart,*restbody; static const char from_expr[]=FROM_EXPR; static const char*fifrom(fromw,lbound,ubound) const char*fromw,*const lbound;char*const ubound; { int i; /* terminate & scan block */ i= *ubound;*ubound='\0';fromw=strstr(mx(fromw,lbound),from_expr);*ubound=i; return fromw; } static int doesc; /* inserts escape characters on outgoing mail */ static long getchunk(s,fromw,len)const int s;const char*fromw;const long len; { static const char esc[]=ESCAP,*ffrom,*endp; if(doesc) /* still something to escape since last time? */ doesc=0,rwrite(s,esc,STRLEN(esc)),lastdump++; /* escape it */ ffrom=0; /* start with a clean slate */ if(fromwrestbody) /* nothing yet? but in range? */ { if((endp+=STRLEN(from_expr)-1)>(ffrom=themail.p+filled)) /* add slack */ endp=(char*)ffrom; /* make sure we stay within bounds */ ffrom=fifrom(fromw,restbody,endp); /* scan body block */ } return ffrom?(doesc=1,(ffrom-fromw)+1L):len; /* +1 to write out the '\n' */ } #ifdef sMAILBOX_SEPARATOR #define smboxseparator(fd) (ft_delim(type)&&\ (part=len,rwrite(fd,sMAILBOX_SEPARATOR,STRLEN(sMAILBOX_SEPARATOR)))) #define MAILBOX_SEPARATOR #else #define smboxseparator(fd) #endif /* sMAILBOX_SEPARATOR */ #ifdef eMAILBOX_SEPARATOR #define emboxseparator(fd) \ (ft_delim(type)&&rwrite(fd,eMAILBOX_SEPARATOR,STRLEN(eMAILBOX_SEPARATOR))) #ifndef MAILBOX_SEPARATOR #define MAILBOX_SEPARATOR #endif #else #define emboxseparator(fd) #endif /* eMAILBOX_SEPARATOR */ long dump(s,type,source,len)const int s,type;const char*source; long len; { int i;long part; lasttell=i= -1;SETerrno(EBADF); if(s>=0) { if(ft_lock(type)&&(lseek(s,(off_t)0,SEEK_END),fdlock(s))) nlog("Kernel-lock failed\n"); lastdump=len;doesc=0; if(ft_delim(type)&&!rawnonl) part=getchunk(s,source,len); /* must escape From_ */ else part=len; lasttell=lseek(s,(off_t)0,SEEK_END); if(!rawnonl) { smboxseparator(s); /* optional separator */ #ifndef NO_NFS_ATIME_HACK /* if it is a file, trick NFS into an */ if(part&&ft_atime(type)) /* a_time wasn't a file */ if(ft_lock(type)) { int serrno=errno; /* save any error information */ if(fdunlock()) nlog("Kernel-unlock failed\n"); SETerrno(serrno); } i=rclose(s)||i; } /* return an error even if nothing was to be sent */ return i&&!len?-1:len; } static int dirfile(chp,linkonly,type)char*const chp;const int linkonly,type; { static const char lkingto[]="Linking to";struct stat stbuf; if(type==ft_MH) { long i=0; /* first let us try to prime i with the */ #ifndef NOopendir /* highest MH folder number we can find */ long j;DIR*dirp;struct dirent*dp;char*chp2; if(dirp=opendir(buf)) { while(dp=readdir(dirp)) /* there still are directory entries */ if((j=strtol(dp->d_name,&chp2,10))>i&&!*chp2) i=j; /* yep, we found a higher number */ closedir(dirp); /* aren't we neat today */ } else readerr(buf); #endif /* NOopendir */ if(chp-buf+sizeNUM(i)>linebuf) exlb: { nlog(exceededlb);setoverflow(); goto ret; } ;{ int ok; do ultstr(0,++i,chp); /* find first empty MH folder */ while((ok=linkonly?rlink(buf2,buf,0):hlink(buf2,buf))&&errno==EEXIST); if(linkonly) { yell(lkingto,buf); if(ok) goto nolnk; goto didlnk; } } goto opn; } else if(type==ft_MAILDIR) { if(!unique(buf,chp,linebuf,NORMperm,verbose,doMAILDIR)) goto ret; unlink(buf); /* found a name, remove file in tmp */ memcpy(chp-MAILDIRLEN-1,maildirnew,MAILDIRLEN); /* but link directly */ } /* into new */ else /* ft_DIR */ { size_t mpl=strlen(msgprefix); if(chp-buf+mpl+sizeNUM(stbuf.st_ino)>linebuf) goto exlb; stat(buf2,&stbuf); /* filename with i-node number */ ultoan((unsigned long)stbuf.st_ino,strcpy(chp,msgprefix)+mpl); } if(linkonly) { yell(lkingto,buf); if(rlink(buf2,buf,0)) /* hardlink the new file, it's a directory folder */ nolnk: nlog("Couldn't make link to"),logqnl(buf); else didlnk: appendlastvar(buf); /* lastvar is "LASTFOLDER" here */ goto ret; } if(!rlink(buf2,buf,0)) /* try rename-via-link */ opn: unlink(buf2); /* success; remove the original */ else if(errno=EEXIST||!stat(buf,&stbuf)||errno!=ENOENT||rename(buf2,buf)) ret: return -1; /* rename it, but only if it won't replace an existing file */ setlastfolder(buf); return opena(buf); } int writefolder(boxname,linkfolder,source,len,ignwerr,dolock) char*boxname,*linkfolder;const char*source;long len;const int ignwerr,dolock; { char*chp,*chp2;mode_t mode;int fd,type; if(*boxname=='|'&&(!linkfolder||linkfolder==Tmnate)) { setlastfolder(boxname); fd=rdup(Deliverymode==2?STDOUT:savstdout); type=ft_PIPE; goto dumpc; } if(boxname!=buf) strcpy(buf,boxname); /* boxname can be found back in buf */ if(linkfolder) /* any additional directories specified? */ { size_t blen; if(blen=Tmnate-linkfolder) /* copy the names into safety */ Tmnate=(linkfolder=tmemmove(malloc(blen),linkfolder,blen))+blen; else linkfolder=0; } type=foldertype(0,0,&mode,0); /* the envelope please! */ chp=strchr(buf,'\0'); switch(type) { case ft_FILE: if(linkfolder) /* any leftovers? Now is the time to display them */ concatenate(linkfolder),skipped(linkfolder),free(linkfolder); if(!strcmp(devnull,buf)) type=ft_PIPE,rawnonl=1; /* save the effort on /dev/null */ else if(!(UPDATE_MASK&(mode|cumask))) chmod(boxname,mode|UPDATE_MASK); if(dolock&&type!=ft_PIPE) { strcpy(chp,lockext); if(!globlock||strcmp(buf,globlock)) lockit(tstrdup(buf),&loclock); *chp='\0'; } setlastfolder(boxname); fd=opena(boxname); dumpc: if(dump(fd,type,source,len)&&!ignwerr) dumpf: { switch(errno) { case ENOSPC:nlog("No space left to finish writing"),logqnl(buf); break; #ifdef EDQUOT case EDQUOT:nlog("Quota exceeded while writing"),logqnl(buf); break; #endif default:writeerr(buf); } if(lasttell>=0&&!truncate(boxname,lasttell)&&(logopened||verbose)) nlog("Truncated file to former size\n"); /* undo garbage */ ret0: return 0; } return 1; case ft_TOOLONG: exlb: nlog(exceededlb);setoverflow(); case ft_CANTCREATE: retf: if(linkfolder) free(linkfolder); goto ret0; case ft_MAILDIR: if(source==themail.p) /* skip leading From_? */ source=skipFrom_(source,&len); strcpy(buf2,buf); chp2=buf2+(chp-buf)-MAILDIRLEN; *chp++= *MCDIRSEP_; ;{ int retries=MAILDIRretries; for(;;) { struct stat stbuf; if(0>(fd=unique(buf,chp,linebuf,NORMperm,verbose,doFD|doMAILDIR))) goto nfail; if(dump(fd,ft_MAILDIR,source,len)&&!ignwerr) goto failed; strcpy(chp2,maildirnew); chp2+=MAILDIRLEN; *chp2++= *MCDIRSEP_; strcpy(chp2,chp); if(!rlink(buf,buf2,0)) { unlink(buf); break; } else if(errno!=EEXIST&&lstat(buf2,&stbuf)&&errno==ENOENT&& !rename(buf,buf2)) break; unlink(buf); if(!retries--) goto nfail; } } setlastfolder(buf2); break; case ft_MH: #if 0 if(source==themail.p) source=skipFrom_(source,&len); #endif default: /* case ft_DIR: */ *chp++= *MCDIRSEP_; strcpy(buf2,buf); chp2=buf2+(chp-buf); if(!unique(buf2,chp2,linebuf,NORMperm,verbose,0)|| 0>(fd=dirfile(chp,0,type))) nfail: { nlog("Couldn't create or rename temp file");logqnl(buf); goto retf; } if(dump(fd,type,source,len)&&!ignwerr) { strcpy(buf,buf2); failed: unlink(buf);lasttell= -1; if(linkfolder) free(linkfolder); goto dumpf; } strcpy(buf2,buf); break; } if(!(UPDATE_MASK&(mode|cumask))) { chp[-1]='\0'; /* restore folder name */ chmod(buf,mode|UPDATE_MASK); } if(linkfolder) /* handle secondary folders */ { for(boxname=linkfolder;boxname!=Tmnate;boxname=strchr(boxname,'\0')+1) { strcpy(buf,boxname); switch(type=foldertype(0,1,&mode,0)) { case ft_TOOLONG:goto exlb; case ft_CANTCREATE:continue; /* just skip it */ case ft_DIR:case ft_MH:case ft_MAILDIR: chp=strchr(buf,'\0'); *chp= *MCDIRSEP_; if(dirfile(chp+1,1,type)) /* link it with the original in buf2 */ if(!(UPDATE_MASK&(mode|cumask))) { *chp='\0'; chmod(buf,mode|UPDATE_MASK); } break; } } free(linkfolder); } return 1; } void logabstract(lstfolder)const char*const lstfolder; { if(lgabstract>0||(logopened||verbose)&&lgabstract) /* don't mail it back? */ { char*chp,*chp2;int i;static const char sfolder[]=FOLDER; if(mailread) /* is the mail completely read in? */ { i= *thebody;*thebody='\0'; /* terminate the header, just in case */ if(eqFrom_(chp=themail.p)) /* any "From " header */ { if(chp=strchr(themail.p,'\n')) *chp='\0'; else chp=thebody; /* preserve mailbox format */ elog(themail.p);elog(newline);*chp='\n'; /* (any length) */ } *thebody=i; /* eliminate the terminator again */ if(!nextexit&& /* don't reenter malloc/free */ (chp=egrepin(NSUBJECT,chp,(long)(thebody-chp),0))) { size_t subjlen; for(chp2= --chp;*--chp2!='\n';); if((subjlen=chp-++chp2)>MAXSUBJECTSHOW) subjlen=MAXSUBJECTSHOW; /* keep it within bounds */ ((char*)tmemmove(buf,chp2,subjlen))[subjlen]='\0';detab(buf); elog(" ");elog(buf);elog(newline); } } elog(sfolder);strlcpy(buf,lstfolder,MAXfoldlen);detab(buf);elog(buf); i=strlen(buf)+STRLEN(sfolder);i-=i%TABWIDTH; /* last dump */ do elog(TABCHAR); while((i+=TABWIDTH)dfilled&&isprivate) /* put it in place here */ { tmemmove(themail.p+dfilled,thebody,filled-=tobesent); tmemmove(themail.p,new.p,dfilled); resizeblock(&themail,filled+=dfilled,1); freeblock(&new); } else /* too big or must share -- switch blocks */ { resizeblock(&new,filled-tobesent+dfilled,0); tmemmove(new.p+dfilled,thebody,filled-=tobesent); freeblock(&themail); themail=new;private(1); filled+=dfilled; } } else { if(!mailread||!filled) rhead=1; /* yup, we read in a new header as well as new mail */ mailread=0;dfilled=thebody-themail.p; if(!isprivate) { memblk new; makeblock(&new,filled); if(filled) tmemmove(new.p,themail.p,filled); freeblock(&themail); themail=new;private(1); } readdyn(&themail,&filled,filled+tobesent); } pastend=filled+(thebody=themail.p); while(thebodythemail.p) chp--; if(contlengthoffset) { unsigned places;long cntlen,actcntlen;charNUM(num,cntlen); chp=themail.p+contlengthoffset;cntlen=filled-(thebody-themail.p); if(filled>1&&themail.p[filled-2]=='\n') /* no phantom '\n'? */ cntlen--; /* make sure it points to the last '\n' */ for(actcntlen=places=0;;) { switch(*chp) { default: /* fill'r up, please */ if(places<=sizeof num-2) *chp++='9',places++,actcntlen=(unsigned long)actcntlen*10+9; else *chp++=' '; /* ultra long Content-Length: field */ continue; case '\n':case '\0':; /* ok, end of the line */ } break; } if(cntlen<=0) /* any Content-Length at all? */ cntlen=0; ultstr(places,cntlen,num); /* our preferred size */ if(!num[places]) /* does it fit in the existing space? */ tmemmove(themail.p+contlengthoffset,num,places),actcntlen=cntlen; chp=thebody+actcntlen; /* skip the actual no we specified */ } restbody=chp;mailread=1; }