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
10 static /*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 
31 int logopened,rawnonl;
32 off_t lasttell;
33 static long lastdump;
34 static volatile int mailread;	/* if the mail is completely read in already */
35 static struct dyna_array confield;		  /* escapes, concatenations */
36 static const char*realstart,*restbody;
37 static const char from_expr[]=FROM_EXPR;
38 
fifrom(fromw,lbound,ubound)39 static 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 
46 static int doesc;
47 			       /* inserts escape characters on outgoing mail */
getchunk(s,fromw,len)48 static 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 
dump(s,type,source,len)80 long 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);
108 jin:	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       }
121 writefin:
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 
dirfile(chp,linkonly,type)134 static 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)
150 exlb: { 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 */
181 nolnk:	nlog("Couldn't make link to"),logqnl(buf);
182      else
183 didlnk: appendlastvar(buf);		     /* lastvar is "LASTFOLDER" here */
184      goto ret;
185    }
186   if(!rlink(buf2,buf,0))			      /* try rename-via-link */
187 opn: unlink(buf2);			     /* success; remove the original */
188   else if(errno=EEXIST||!stat(buf,&stbuf)||errno!=ENOENT||rename(buf2,buf))
189 ret: return -1;	 /* rename it, but only if it won't replace an existing file */
190   setlastfolder(buf);
191   return opena(buf);
192 }
193 
writefolder(boxname,linkfolder,source,len,ignwerr,dolock)194 int 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);
230 dumpc:	if(dump(fd,type,source,len)&&!ignwerr)
231 dumpf:	 { 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 */
242 ret0:	   return 0;
243 	 }
244 	return 1;
245      case ft_TOOLONG:
246 exlb:	nlog(exceededlb);setoverflow();
247      case ft_CANTCREATE:
248 retf:	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)))
293 nfail:	 { 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);
298 failed:	   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 
logabstract(lstfolder)332 void 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 
363 static int concnd;				 /* last concatenation value */
364 
concon(ch)365 void 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 
readmail(rhead,tobesent)374 void 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);
382 	readdyn(&new,&dfilled,thebody-themail.p);
383 	if(tobesent>dfilled&&isprivate)		     /* put it in place here */
384 	 { tmemmove(themail.p+dfilled,thebody,filled-=tobesent);
385 	   tmemmove(themail.p,new.p,dfilled);
386 	   resizeblock(&themail,filled+=dfilled,1);
387 	   freeblock(&new);
388 	 }
389 	else			   /* too big or must share -- switch blocks */
390 	 { resizeblock(&new,filled-tobesent+dfilled,0);
391 	   tmemmove(new.p+dfilled,thebody,filled-=tobesent);
392 	   freeblock(&themail);
393 	   themail=new;private(1);
394 	   filled+=dfilled;
395 	 }
396       }
397      else
398       { if(!mailread||!filled)
399 	   rhead=1;	 /* yup, we read in a new header as well as new mail */
400 	mailread=0;dfilled=thebody-themail.p;
401 	if(!isprivate)
402 	 { memblk new;
403 	   makeblock(&new,filled);
404 	   if(filled)
405 	      tmemmove(new.p,themail.p,filled);
406 	   freeblock(&themail);
407 	   themail=new;private(1);
408 	 }
409 	readdyn(&themail,&filled,filled+tobesent);
410       }
411      pastend=filled+(thebody=themail.p);
412      while(thebody<pastend&&*thebody++=='\n');	     /* skip leading garbage */
413      realstart=thebody;
414      if(rhead)			      /* did we read in a new header anyway? */
415       { confield.filled=0;concnd='\n';
416 	while(thebody=strchr(thebody,'\n'))
417 	   switch(*++thebody)			    /* mark continued fields */
418 	    { case '\t':case ' ':app_vall(confield,(long)(thebody-1-themail.p));
419 	      default:
420 		 continue;		   /* empty line marks end of header */
421 	      case '\n':thebody++;
422 		 goto eofheader;
423 	    }
424 	thebody=pastend;      /* provide a default, in case there is no body */
425 eofheader:
426 	contlengthoffset=0;		      /* traditional Berkeley format */
427 	if(!berkeley&&				  /* ignores Content-Length: */
428 	   (chp=egrepin("^Content-Length:",themail.p,
429 			(long)(thebody-themail.p),0)))
430 	   contlengthoffset=chp-themail.p;
431       }
432      else			       /* no new header read, keep it simple */
433 	thebody=themail.p+dfilled; /* that means we know where the body starts */
434    }		      /* to make sure that the first From_ line is uninjured */
435   if((chp=thebody)>themail.p)
436      chp--;
437   if(contlengthoffset)
438    { unsigned places;long cntlen,actcntlen;charNUM(num,cntlen);
439      chp=themail.p+contlengthoffset;cntlen=filled-(thebody-themail.p);
440      if(filled>1&&themail.p[filled-2]=='\n')		 /* no phantom '\n'? */
441 	cntlen--;		     /* make sure it points to the last '\n' */
442      for(actcntlen=places=0;;)
443       { switch(*chp)
444 	 { default:					/* fill'r up, please */
445 	      if(places<=sizeof num-2)
446 		 *chp++='9',places++,actcntlen=(unsigned long)actcntlen*10+9;
447 	      else
448 		 *chp++=' ';		 /* ultra long Content-Length: field */
449 	      continue;
450 	   case '\n':case '\0':;		      /* ok, end of the line */
451 	 }
452 	break;
453       }
454      if(cntlen<=0)			       /* any Content-Length at all? */
455 	cntlen=0;
456      ultstr(places,cntlen,num);			       /* our preferred size */
457      if(!num[places])		       /* does it fit in the existing space? */
458 	tmemmove(themail.p+contlengthoffset,num,places),actcntlen=cntlen;
459      chp=thebody+actcntlen;		  /* skip the actual no we specified */
460    }
461   restbody=chp;mailread=1;
462 }
463