chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-return.c
1 /*$Id: ezmlm-return.c,v 1.26 1999/08/07 20:50:52 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3 #include <sys/types.h>
4 #include "direntry.h"
5 #include "stralloc.h"
6 #include "str.h"
7 #include "env.h"
8 #include "sig.h"
9 #include "slurp.h"
10 #include "getconf.h"
11 #include "strerr.h"
12 #include "byte.h"
13 #include "case.h"
14 #include "getln.h"
15 #include "substdio.h"
16 #include "error.h"
17 #include "quote.h"
18 #include "readwrite.h"
19 #include "fmt.h"
20 #include "now.h"
21 #include "cookie.h"
22 #include "subscribe.h"
23 #include "errtxt.h"
24 #include "idx.h"
25
26 #define FATAL "ezmlm-return: fatal: "
27 #define INFO "ezmlm-return: info: "
28 void die_usage()
29 { strerr_die1x(100,"ezmlm-return: usage: ezmlm-return [-dD] dir"); }
30 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
31 void die_badaddr()
32 {
33   strerr_die2x(100,FATAL,ERR_BAD_RETURN_ADDRESS);
34 }
35 void die_trash()
36 {
37   strerr_die2x(99,INFO,"trash address");
38 }
39
40 char outbuf[1024];
41 substdio ssout;
42 char inbuf[1024];
43 substdio ssin;
44
45 char strnum[FMT_ULONG];
46 char hash[COOKIE];
47 char hashcopy[COOKIE];
48 char *hashp = (char *) 0;
49 unsigned long cookiedate;
50 unsigned long addrno = 0L;
51 unsigned long addrno1 = 0L;
52 stralloc fndir = {0};
53 stralloc fndate = {0};
54 stralloc fndatenew = {0};
55 stralloc fnhash = {0};
56 stralloc fnhashnew = {0};
57 void *psql = (void *) 0;
58
59 stralloc quoted = {0};
60 stralloc ddir = {0};
61 char *sender;
62 char *dir;
63 char *workdir;
64
65 void die_hashnew()
66 { strerr_die4sys(111,FATAL,ERR_WRITE,fnhashnew.s,": "); }
67 void die_datenew()
68 { strerr_die4sys(111,FATAL,ERR_WRITE,fndatenew.s,": "); }
69 void die_msgin()
70 { strerr_die2sys(111,FATAL,ERR_READ_INPUT); }
71
72 void makedir(s)
73 char *s;
74 {
75   if (mkdir(s,0755) == -1)
76     if (errno != error_exist)
77       strerr_die4x(111,FATAL,ERR_CREATE,s,": ");
78 }
79
80 void dowit(addr,when,bounce)
81 char *addr;
82 unsigned long when;
83 stralloc *bounce;
84 {
85   int fd;
86   unsigned int wpos;
87   unsigned long wdir,wfile;
88
89   if (!issub(workdir,addr,(char *) 0,FATAL)) return;
90
91   if (!stralloc_copys(&fndate,workdir)) die_nomem();
92   if (!stralloc_cats(&fndate,"/bounce/d")) die_nomem();
93   if (!stralloc_0(&fndate)) die_nomem();
94   fndate.s[fndate.len - 1] = '/';       /* replace '\0' */
95   wpos = fndate.len - 1;
96   wdir = when / 10000;
97   wfile = when - 10000 * wdir;
98   if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,wdir))) die_nomem();
99   if (!stralloc_0(&fndate)) die_nomem();
100   makedir(fndate.s);
101   --fndate.len;                         /* remove terminal '\0' */
102   if (!stralloc_cats(&fndate,"/w")) die_nomem();
103   wpos = fndate.len - 1;
104   if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,wfile))) die_nomem();
105   if (!stralloc_cats(&fndate,".")) die_nomem();
106   if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,(unsigned long) getpid())))
107         die_nomem();
108   if (!stralloc_0(&fndate)) die_nomem();
109   if (!stralloc_copy(&fndatenew,&fndate)) die_nomem();
110   fndatenew.s[wpos] = 'W';
111
112   fd = open_trunc(fndatenew.s);
113   if (fd == -1) die_datenew();
114   substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
115   if (substdio_puts(&ssout,addr) == -1) die_datenew();
116   if (substdio_put(&ssout,"",1) == -1) die_datenew();
117   if (substdio_puts(&ssout,"Return-Path: <") == -1) die_datenew();
118   if (!quote2(&quoted,sender)) die_nomem();
119   if (substdio_put(&ssout,quoted.s,quoted.len) == -1) die_datenew();
120   if (substdio_puts(&ssout,">\n") == -1) die_datenew();
121   if (substdio_put(&ssout,bounce->s,bounce->len) == -1) die_datenew();
122   if (substdio_flush(&ssout) == -1) die_datenew();
123   if (fsync(fd) == -1) die_datenew();
124   if (close(fd) == -1) die_datenew(); /* NFS stupidity */
125
126   if (rename(fndatenew.s,fndate.s) == -1)
127     strerr_die6sys(111,FATAL,ERR_MOVE,fndatenew.s," to ",fndate.s,": ");
128 }
129
130 void doit(addr,msgnum,when,bounce)
131 char *addr;
132 unsigned long msgnum;
133 unsigned long when;
134 stralloc *bounce;
135 {
136   int fd;
137   int fdnew;
138   unsigned int pos;
139   unsigned long ddir,dfile;
140
141   if (!issub(workdir,addr,(char *) 0,FATAL)) return;
142
143   if (!stralloc_copys(&fndate,workdir)) die_nomem();
144   if (!stralloc_cats(&fndate,"/bounce/d")) die_nomem();
145   if (!stralloc_0(&fndate)) die_nomem();
146   makedir(fndate.s);
147   fndate.s[fndate.len-1] = '/';         /* replace terminal '\0' */
148   ddir = when / 10000;
149   dfile = when - 10000 * ddir;
150   if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,ddir))) die_nomem();
151   if (!stralloc_copy(&fndir,&fndate)) die_nomem();
152   if (!stralloc_0(&fndir)) die_nomem(); /* make later if necessary (new addr)*/
153   if (!stralloc_cats(&fndate,"/d")) die_nomem();
154   pos = fndate.len - 2;
155   if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,dfile))) die_nomem();
156   if (!stralloc_cats(&fndate,".")) die_nomem();
157   if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,(unsigned long) getpid())))
158          die_nomem();
159   if (addrno) { /* so that pre-VERP bounces make a d... file per address */
160                 /* for the first one we use the std-style fname */
161     if (!stralloc_cats(&fndate,".")) die_nomem();
162     if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,addrno))) die_nomem();
163   }
164   addrno++;     /* get ready for next */
165   if (!stralloc_0(&fndate)) die_nomem();
166   if (!stralloc_copy(&fndatenew,&fndate)) die_nomem();
167   fndatenew.s[pos] = '_';       /* fndate = bounce/d/nnnn/dmmmmmm */
168                                 /* fndatenew = bounce/d/nnnn_dmmmmmm */
169
170   fd = open_trunc(fndatenew.s);
171   if (fd == -1) die_datenew();
172   substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
173   if (substdio_puts(&ssout,addr) == -1) die_datenew();
174   if (substdio_put(&ssout,"",1) == -1) die_datenew();
175   if (substdio_puts(&ssout,"Return-Path: <") == -1) die_datenew();
176   if (!quote2(&quoted,sender)) die_nomem();
177   if (substdio_put(&ssout,quoted.s,quoted.len) == -1) die_datenew();
178   if (substdio_puts(&ssout,">\n") == -1) die_datenew();
179   if (substdio_put(&ssout,bounce->s,bounce->len) == -1) die_datenew();
180   if (substdio_flush(&ssout) == -1) die_datenew();
181   if (fsync(fd) == -1) die_datenew();
182   if (close(fd) == -1) die_datenew(); /* NFS stupidity */
183
184   cookie(hash,"",0,"",addr,"");
185   if (!stralloc_copys(&fnhash,workdir)) die_nomem();
186   if (!stralloc_cats(&fnhash,"/bounce/h")) die_nomem();
187   if (!stralloc_0(&fnhash)) die_nomem();
188   makedir(fnhash.s);
189   fnhash.s[fnhash.len - 1] = '/';               /* replace terminal '\0' */
190   if (!stralloc_catb(&fnhash,hash,1)) die_nomem();
191   if (!stralloc_0(&fnhash)) die_nomem();
192   makedir(fnhash.s);
193   --fnhash.len;                                 /* remove terminal '\0' */
194   if (!stralloc_cats(&fnhash,"/h")) die_nomem();
195   pos = fnhash.len - 1;
196   if (!stralloc_catb(&fnhash,hash+1,COOKIE-1)) die_nomem();
197   if (!stralloc_0(&fnhash)) die_nomem();
198   if (!stralloc_copy(&fnhashnew,&fnhash)) die_nomem();
199   fnhashnew.s[pos] = 'H';
200
201   fdnew = open_trunc(fnhashnew.s);
202   if (fdnew == -1) die_hashnew();
203   substdio_fdbuf(&ssout,write,fdnew,outbuf,sizeof(outbuf));
204
205   fd = open_read(fnhash.s);
206   if (fd == -1) {
207     if (errno != error_noent)
208       strerr_die4sys(111,FATAL,ERR_READ,fnhash.s,": ");
209     makedir(fndir.s);
210     if (rename(fndatenew.s,fndate.s) == -1)
211       strerr_die6sys(111,FATAL,ERR_MOVE,fndatenew.s," to ",fndate.s,": ");
212   }
213   else {
214     substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
215     switch(substdio_copy(&ssout,&ssin)) {
216       case -2: die_msgin();
217       case -3: die_hashnew();
218     }
219     close(fd);
220     if (unlink(fndatenew.s) == -1)
221       strerr_die4sys(111,FATAL,ERR_DELETE,fndatenew.s,": ");
222   }
223   if (substdio_puts(&ssout,"   ") == -1) die_hashnew();
224   if (substdio_put(&ssout,strnum,fmt_ulong(strnum,msgnum)) == -1) die_hashnew();
225   if (substdio_puts(&ssout,"\n") == -1) die_hashnew();
226   if (substdio_flush(&ssout) == -1) die_hashnew();
227   if (fsync(fdnew) == -1) die_hashnew();
228   if (close(fdnew) == -1) die_hashnew(); /* NFS stupidity */
229
230   if (rename(fnhashnew.s,fnhash.s) == -1)
231     strerr_die6sys(111,FATAL,ERR_MOVE,fnhashnew.s," to ",fnhash.s,": ");
232 }
233
234 stralloc bounce = {0};
235 stralloc line = {0};
236 stralloc header = {0};
237 stralloc intro = {0};
238 stralloc failure = {0};
239 stralloc paragraph = {0};
240 int flagmasterbounce = 0;
241 int flaghaveheader;
242 int flaghaveintro;
243
244 stralloc key = {0};
245
246 char msginbuf[1024];
247 substdio ssmsgin;
248
249 void main(argc,argv)
250 int argc;
251 char **argv;
252 {
253   char *local;
254   char *action;
255   char *def;
256   char *ret;
257   char *cp;
258   unsigned long msgnum;
259   unsigned long cookiedate;
260   unsigned long when;
261   unsigned long listno = 0L;
262   int match;
263   unsigned int i;
264   int flagdig = 0;
265   int flagmaster = 0;
266   int flagreceipt = 0;
267   int fdlock;
268   register char ch;
269
270   umask(022);
271   sig_pipeignore();
272   when = (unsigned long) now();
273
274   dir = argv[1];
275   if (!dir) die_usage();
276   if (*dir == '-') {                    /* for normal use */
277     if (dir[1] == 'd') {
278       flagdig = 1;
279     } else if (dir[1] == 'D') {
280       flagdig = 0;
281     } else
282       die_usage();
283     dir = argv[2];
284     if (!dir) die_usage();
285   }
286
287   sender = env_get("SENDER");
288   if (!sender) strerr_die2x(100,FATAL,ERR_NOSENDER);
289   local = env_get("LOCAL");
290   if (!local) strerr_die2x(100,FATAL,ERR_NOLOCAL);
291   def = env_get("DEFAULT");             /* qmail-1.02 */
292
293   if (chdir(dir) == -1)
294     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
295
296   switch(slurp("key",&key,32)) {
297     case -1:
298       strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
299     case 0:
300       strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
301   }
302   workdir = dir;
303   action = def;
304
305     if (str_start(action,"receipt-")) {
306       flagreceipt = 1;
307       action += 8;
308     }
309     ch = *action;               /* -d -digest, -m -master, -g -getmaster */
310     if (ch && action[1] == '-') {
311       switch (ch) {
312         case 'g': flagmaster = 1; flagdig = 1; action += 2; break;
313         case 'm': flagmaster = 1; action += 2; break;
314         default: break;
315       }
316     }
317   if (flagdig) {
318     if (!stralloc_copys(&ddir,dir)) die_nomem();
319     if (!stralloc_cats(&ddir,"/digest")) die_nomem();
320     if (!stralloc_0(&ddir)) die_nomem();
321     workdir = ddir.s;
322   }
323
324   if (!*action) die_trash();
325
326   if (flagreceipt || flagmaster)                        /* check cookie */
327     if (str_chr(action,'-') == COOKIE) {
328       action[COOKIE] = '\0';
329       hashp = action;
330       action += COOKIE + 1;
331   }
332
333   if (!flagreceipt) {
334     if (!flagmaster && str_start(action,"probe-")) {
335       action += 6;
336       action += scan_ulong(action,&cookiedate);
337       if (now() - cookiedate > 3000000) die_trash();
338       if (*action++ != '.') die_trash();
339       i = str_chr(action,'-');
340       if (i != COOKIE) die_trash();
341       byte_copy(hashcopy,COOKIE,action);
342       action += COOKIE;
343       if (*action++ != '-') die_trash();
344       i = str_rchr(action,'=');
345       if (!stralloc_copyb(&line,action,i)) die_nomem();
346       if (action[i]) {
347         if (!stralloc_cats(&line,"@")) die_nomem();
348         if (!stralloc_cats(&line,action + i + 1)) die_nomem();
349       }
350       if (!stralloc_0(&line)) die_nomem();
351       strnum[fmt_ulong(strnum,cookiedate)] = 0;
352       cookie(hash,key.s,key.len,strnum,line.s,"P");
353       if (byte_diff(hash,COOKIE,hashcopy)) die_trash();
354
355       (void) subscribe(workdir,line.s,0,"","-probe",1,-1,(char *) 0,FATAL);
356       _exit(99);
357     }
358
359     if (!stralloc_copys(&line,workdir)) die_nomem();
360     if (!stralloc_cats(&line,"/lockbounce")) die_nomem();
361     if (!stralloc_0(&line)) die_nomem();
362
363     fdlock = open_append(line.s);
364     if (fdlock == -1)
365       strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
366     if (lock_ex(fdlock) == -1)
367       strerr_die4sys(111,FATAL,ERR_OBTAIN,line.s,": ");
368
369     if (!flagmaster && str_start(action,"warn-")) {
370       action += 5;
371       action += scan_ulong(action,&cookiedate);
372       if (now() - cookiedate > 3000000) die_trash();
373       if (*action++ != '.') die_trash();
374       i = str_chr(action,'-');
375       if (i != COOKIE) die_trash();
376       byte_copy(hashcopy,COOKIE,action);
377       action += COOKIE;
378       if (*action++ != '-') die_trash();
379       i = str_rchr(action,'=');
380       if (!stralloc_copyb(&line,action,i)) die_nomem();
381       if (action[i]) {
382         if (!stralloc_cats(&line,"@")) die_nomem();
383         if (!stralloc_cats(&line,action + i + 1)) die_nomem();
384       }
385       if (!stralloc_0(&line)) die_nomem();
386       strnum[fmt_ulong(strnum,cookiedate)] = 0;
387       cookie(hash,key.s,key.len,strnum,line.s,"W");
388       if (byte_diff(hash,COOKIE,hashcopy)) die_trash();
389
390       if (slurpclose(0,&bounce,1024) == -1) die_msgin();
391       dowit(line.s,when,&bounce);
392       _exit(99);
393     }
394   }
395   action += scan_ulong(action,&msgnum);
396   if (*action++ != '-') die_badaddr();
397   cp = action;
398   if (*action >= '0' && *action <= '9') {               /* listno */
399     action += scan_ulong(action,&listno);
400     listno++;                                   /* logging is 1-53, not 0-52 */
401   }
402
403   if (hashp) {          /* scrap bad cookies */
404       if ((ret = checktag(workdir,msgnum,0L,"x",(char *) 0,hashp))) {
405         if (*ret)
406           strerr_die2x(111,FATAL,*ret);
407         else
408           die_trash();
409       } else if (flagreceipt) {
410         if (!(ret = logmsg(dir,msgnum,listno,0L,5))) {
411           closesql();
412           strerr_die6x(99,INFO,"receipt:",cp," [",hashp,"]");
413         }
414         if (*ret) strerr_die2x(111,FATAL,ret);
415         else strerr_die2x(0,INFO,ERR_DONE);
416       } else if (*action) {     /* post VERP master bounce */
417         if ((ret = logmsg(dir,msgnum,listno,0L,-1))) {
418           closesql();
419           strerr_die4x(0,INFO,"bounce [",hashp,"]");
420         }
421         if (*ret) strerr_die2x(111,FATAL,ret);
422         else strerr_die2x(99,INFO,ERR_DONE);
423       }
424    } else if (flagreceipt || flagmaster)
425         die_badaddr();
426
427   if (*action) {
428     i = str_rchr(action,'=');
429     if (!stralloc_copyb(&line,action,i)) die_nomem();
430     if (action[i]) {
431       if (!stralloc_cats(&line,"@")) die_nomem();
432       if (!stralloc_cats(&line,action + i + 1)) die_nomem();
433     }
434     if (!stralloc_0(&line)) die_nomem();
435     if (slurpclose(0,&bounce,1024) == -1) die_msgin();
436     doit(line.s,msgnum,when,&bounce);
437     _exit(99);
438   }
439
440   /* pre-VERP bounce, in QSBMF format. Receipts are never pre-VERP */
441
442   substdio_fdbuf(&ssmsgin,read,0,msginbuf,sizeof(msginbuf));
443
444   flaghaveheader = 0;
445   flaghaveintro = 0;
446
447   for (;;) {
448     if (!stralloc_copys(&paragraph,"")) die_nomem();
449     for (;;) {
450       if (getln(&ssmsgin,&line,&match,'\n') == -1) die_msgin();
451       if (!match) die_trash();
452       if (!stralloc_cat(&paragraph,&line)) die_nomem();
453       if (line.len <= 1) break;
454     }
455
456     if (!flaghaveheader) {
457       if (!stralloc_copy(&header,&paragraph)) die_nomem();
458       flaghaveheader = 1;
459       continue;
460     }
461
462     if (!flaghaveintro) {
463       if (paragraph.s[0] == '-' && paragraph.s[1] == '-')
464         continue;               /* skip MIME boundary if it exists */
465       if (paragraph.len < 15) die_trash();
466       if (str_diffn(paragraph.s,"Hi. This is the",15)) die_trash();
467       if (!stralloc_copy(&intro,&paragraph)) die_nomem();
468       flaghaveintro = 1;
469       continue;
470     }
471
472     if (paragraph.s[0] == '-')
473       break;
474
475     if (paragraph.s[0] == '<') {
476       if (!stralloc_copy(&failure,&paragraph)) die_nomem();
477
478       if (!stralloc_copy(&bounce,&header)) die_nomem();
479       if (!stralloc_cat(&bounce,&intro)) die_nomem();
480       if (!stralloc_cat(&bounce,&failure)) die_nomem();
481
482       i = byte_chr(failure.s,failure.len,'\n');
483       if (i < 3) die_trash();
484
485       if (!stralloc_copyb(&line,failure.s + 1,i - 3)) die_nomem();
486       if (byte_chr(line.s,line.len,'\0') == line.len) {
487         if (!stralloc_0(&line)) die_nomem();
488         if (flagmaster) {                               /* bounced msg slave! */
489           if ((i = str_rchr(line.s,'@')) >= 5) {        /* 00_52@host */
490             line.s[i] = '\0';                           /* 00_52 */
491             (void) scan_ulong(line.s + i - 5,&listno);
492               if ((ret = logmsg(dir,msgnum,listno + 1,0L,-1)) && *ret)
493                 strerr_die2x(111,FATAL,ret);
494             strerr_warn3(INFO,"bounce ",line.s + i - 5,(struct strerr *) 0);
495             flagmasterbounce = 1;
496           }
497         } else
498           doit(line.s,msgnum,when,&bounce);
499       }
500     }
501   }
502   closesql();
503   if (flagmasterbounce)
504     strerr_die3x(0,"[",hashp,"]");
505   else
506     _exit(99);
507 }