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