chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-receipt.c
1 /*$Id: ezmlm-receipt.c,v 1.10 1999/02/05 04:57:44 lindberg Exp $*/
2 /*$Name: ezmlm-idx-0324 $*/
3 /* Handles receipts and bounces from sublists at the main list */
4 /* Set up instead of ezmlm-return in DIR/bouncer of main list */
5
6 #include <sys/types.h>
7 #include "direntry.h"
8 #include "stralloc.h"
9 #include "str.h"
10 #include "env.h"
11 #include "slurp.h"
12 #include "getconf.h"
13 #include "strerr.h"
14 #include "byte.h"
15 #include "case.h"
16 #include "quote.h"
17 #include "getln.h"
18 #include "substdio.h"
19 #include "error.h"
20 #include "readwrite.h"
21 #include "fmt.h"
22 #include "now.h"
23 #include "seek.h"
24 #include "idx.h"
25 #include "errtxt.h"
26
27 #define FATAL "ezmlm-receipt: fatal: "
28 #define INFO "ezmlm-receipt: info: "
29
30 void die_usage()
31 {
32   strerr_die1x(100,"ezmlm-receipt: usage: ezmlm-receipt [-dD] dir");
33 }
34
35 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
36
37 void die_badaddr()
38 {
39   strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
40 }
41 void die_trash()
42 {
43   strerr_die2x(0,INFO,"trash address");
44 }
45
46 stralloc line = {0};
47 stralloc quoted = {0};
48 stralloc intro = {0};
49 stralloc bounce = {0};
50 stralloc header = {0};
51 stralloc failure = {0};
52 stralloc paragraph = {0};
53 stralloc ddir = {0};
54 stralloc outhost = {0};
55 stralloc outlocal = {0};
56 stralloc inlocal = {0};
57 stralloc tagline = {0};
58 stralloc listaddr = {0};
59 stralloc fndate = {0};
60 stralloc fndir = {0};
61 stralloc fndatenew = {0};
62
63 void die_datenew()
64 { strerr_die4sys(111,FATAL,ERR_WRITE,fndatenew.s,": "); }
65 void die_msgin()
66 { strerr_die2sys(111,FATAL,ERR_READ_INPUT); }
67
68 char strnum[FMT_ULONG];
69 char inbuf[1024];
70 substdio ssin;
71
72 char outbuf[256];       /* small - rarely used */
73 substdio ssout;
74
75 unsigned long when;
76 unsigned long addrno = 0L;
77
78 char *sender;
79 char *dir;
80 char *workdir;
81 void **psql = (void **) 0;
82 stralloc listno = {0};
83
84
85 void doit(addr,msgnum,when,bounce)
86 /* Just stores address\0nsgnum\0 followed by bounce. File name is          */
87 /* dttt.ppp[.n], where 'ttt' is a time stamp, 'ppp' the pid, and 'n' the   */
88 /* number when there are more than 1 addresses in a pre-VERP bounce. In    */
89 /* this case, the first one is just dttt.ppp, the decond dttt.ppp.2, etc.  */
90 /* For a main list, bounces come from sublists. They are rare and serious. */
91 char *addr;
92 unsigned long msgnum;
93 unsigned long when;
94 stralloc *bounce;
95 {
96   int fd;
97   unsigned int pos;
98   DIR *bouncedir;
99   direntry *d;
100   unsigned int no;
101
102   if (!stralloc_copys(&fndir,workdir)) die_nomem();
103   if (!stralloc_cats(&fndir,"/bounce")) die_nomem();
104   if (!stralloc_0(&fndir)) die_nomem();
105   bouncedir = opendir(fndir.s);
106   if (!bouncedir)
107     if (errno != error_noent)
108       strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
109     else
110       strerr_die3x(111,FATAL,fndir.s,ERR_NOEXIST);
111
112   no = MAX_MAIN_BOUNCES;        /* no more than this many allowed */
113   while (no && (d = readdir(bouncedir))) {
114     if (str_equal(d->d_name,".")) continue;
115     if (str_equal(d->d_name,"..")) continue;
116     --no;
117   }
118   closedir(bouncedir);
119   if (!no)                      /* max no of bounces exceeded */
120     strerr_die2x(0,INFO,ERR_MAX_BOUNCE);
121                                 /* save bounce */
122   if (!stralloc_copys(&fndate,workdir)) die_nomem();
123   if (!stralloc_cats(&fndate,"/bounce/d")) die_nomem();
124   pos = fndate.len - 1;
125   if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,when))) die_nomem();
126   if (!stralloc_cats(&fndate,".")) die_nomem();
127   if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,(unsigned long) getpid())))
128          die_nomem();
129   if (addrno) { /* so that pre-VERP bounces make a d... file per address */
130                 /* for the first one we use the std-style fname */
131     if (!stralloc_cats(&fndate,".")) die_nomem();
132     if (!stralloc_catb(&fndate,strnum,fmt_ulong(strnum,addrno))) die_nomem();
133   }
134   addrno++;     /* get ready for next */
135   if (!stralloc_0(&fndate)) die_nomem();
136   if (!stralloc_copy(&fndatenew,&fndate)) die_nomem();
137   fndatenew.s[pos] = 'D';
138
139   fd = open_trunc(fndatenew.s);
140   if (fd == -1) die_datenew();
141   substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
142   if (substdio_puts(&ssout,addr) == -1) die_datenew();
143   if (substdio_put(&ssout,"",1) == -1) die_datenew();
144   if (substdio_put(&ssout,strnum,fmt_ulong(strnum,msgnum)) == -1)
145         die_datenew();
146   if (substdio_put(&ssout,"",1) == -1) die_datenew();
147
148   if (substdio_puts(&ssout,"Return-Path: <") == -1) die_datenew();
149   if (!quote2(&quoted,sender)) die_nomem();
150   if (substdio_put(&ssout,quoted.s,quoted.len) == -1) die_datenew();
151   if (substdio_puts(&ssout,">\n") == -1) die_datenew();
152   if (substdio_put(&ssout,bounce->s,bounce->len) == -1) die_datenew();
153   if (substdio_flush(&ssout) == -1) die_datenew();
154   if (fsync(fd) == -1) die_datenew();
155   if (close(fd) == -1) die_datenew(); /* NFS stupidity */
156   if (rename(fndatenew.s,fndate.s) == -1)
157     strerr_die6sys(111,FATAL,ERR_MOVE,fndatenew.s," to ",fndate.s,": ");
158 }
159
160 void main(argc,argv)
161 int argc;
162 char **argv;
163 {
164   char *local;
165   char *host;
166   char *action;
167   char *def;
168   int flagdig = 1;
169   int flaghaveintro;
170   int flaghaveheader;
171   int match;
172   unsigned long msgnum;
173   unsigned int i;
174   unsigned int len;
175   char *cp;
176
177   umask(022);
178   sig_pipeignore();
179
180   when = (unsigned long) now();
181
182   dir = argv[1];
183   if (!dir) die_usage();
184   if (*dir == '-') {
185     if (dir[1] == 'd') {
186       flagdig = 2;
187     } else if (dir[1] == 'D') {
188       flagdig = 0;
189     } else
190       die_usage();
191     dir = argv[2];
192     if (!dir) die_usage();
193   }
194   if (chdir(dir) == -1)
195     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
196
197   sender = env_get("SENDER");
198   def = env_get("DEFAULT");
199   local = env_get("LOCAL");
200
201   getconf_line(&outhost,"outhost",1,FATAL,dir);
202   getconf_line(&outlocal,"outlocal",1,FATAL,dir);
203   workdir = dir;
204   if (def) {                            /* qmail>=1.02 */
205     action = def;                       /* now see if -digest-return- */
206     if (flagdig == 1) {
207       flagdig = 0;
208       if (str_len(local) >= str_len(def) + 14)
209         if (str_start(local + str_len(local) - 14 - str_len(def),"digest-"))
210           flagdig = 2;
211     }
212   } else {                              /* older version of qmail */
213     getconf_line(&inlocal,"inlocal",1,FATAL,dir);
214     if (inlocal.len > str_len(local)) die_badaddr();
215     if (case_diffb(inlocal.s,inlocal.len,local)) die_badaddr();
216     action = local + inlocal.len;
217     if (flagdig == 1) {
218       flagdig = 0;
219       if (str_start(action,"-digest")) {
220         flagdig = 2;
221         action += 7;
222       }
223     }
224     if (!str_start(action,"-return-")) die_badaddr();
225     action += 8;
226   }
227   if (flagdig) {
228     if (!stralloc_copys(&ddir,dir)) die_nomem();
229     if (!stralloc_cats(&ddir,"/digest")) die_nomem();
230     if (!stralloc_0(&ddir)) die_nomem();
231     workdir = ddir.s;
232     if (!stralloc_cats(&outlocal,"-digest")) die_nomem();
233   }
234   if (!*action) die_trash();
235
236   substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
237
238   if (!case_diffs(action,"receipt")) {
239     host = sender + str_rchr(sender,'@');
240     if (*host)
241       *(host++) = '\0';
242     cp = sender;
243                                 /* check recipient in case it's a bounce*/
244     while (*(cp++)) {           /* decode sender */
245       cp += str_chr(cp,'-');
246       if (case_starts(cp,"-return-")) {
247         if (!scan_ulong(cp + 8,&msgnum))
248           strerr_die2x(100,FATAL,"bad VERP format for receipt");
249          *cp = '\0';
250         if (!stralloc_copys(&listaddr,sender)) die_nomem();
251         if (!stralloc_append(&listaddr,"@")) die_nomem();
252         if (!stralloc_cats(&listaddr,host)) die_nomem();
253         if (!stralloc_0(&listaddr)) die_nomem();
254         break;
255       }
256     }
257     for(;;) {                                           /* Get X-tag from hdr*/
258       if (getln(&ssin,&line,&match,'\n') == -1) die_msgin();
259       if (!match)
260         break;
261
262       if (line.len == 1) break;
263       if (case_startb(line.s,line.len,TXT_TAG)) {
264         len = str_len(TXT_TAG);
265         if (!stralloc_catb(&tagline,line.s +len,line.len - len -1)) die_nomem();
266                 /* NOTE: tagline is dirty! We quote it for sql and rely on */
267                 /* std log clean for maillog */
268         break;
269       }
270     }
271                 /* feedback ok even if not sub. Will be filtered by subreceipt*/
272                 /* For instance, main list feedback is ok, but !issub. */
273     subreceipt(workdir,msgnum,&tagline,listaddr.s,2,INFO,FATAL);
274     closesql();
275     _exit(0);
276   }
277                                 /* not receipt - maybe bounce */
278                                 /* no need to lock. dttt.pid can be assumed */
279                                 /* to be unique and if not would be over- */
280                                 /* written even with lock */
281   action += scan_ulong(action,&msgnum);
282   if (*action != '-') die_badaddr();
283   ++action;
284                 /* scan bounce for tag. It'll be in the BODY! */
285   for (;;) {
286     if (getln(&ssin,&line,&match,'\n') == -1)
287       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
288     if (!match) break;
289     if (case_startb(line.s,line.len,TXT_TAG)) {
290       len = str_len(TXT_TAG);
291       if (!stralloc_catb(&tagline,line.s +len,line.len - len -1)) die_nomem();
292                 /* NOTE: tagline is dirty! We quote it for sql and rely on */
293                 /* std log clean for maillog */
294       break;
295     }
296   }
297   if (seek_begin(0) == -1)
298     strerr_die2sys(111,FATAL,ERR_SEEK_INPUT);
299   substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
300
301   if (*action) {        /* normal bounce */
302
303     if (slurpclose(0,&bounce,1024) == -1) die_msgin();
304     i = str_rchr(action,'=');
305     if (!stralloc_copyb(&listaddr,action,i)) die_nomem();
306     if (action[i]) {
307       if (!stralloc_cats(&listaddr,"@")) die_nomem();
308       if (!stralloc_cats(&listaddr,action + i + 1)) die_nomem();
309     }
310     if (!stralloc_0(&listaddr)) die_nomem();
311                 /* don't check for sub, since issub() doesn't see sublists */
312     switch (subreceipt(workdir,msgnum,&tagline,listaddr.s,-1,INFO,FATAL)) {
313         case -1: strerr_die2x(0,INFO,ERR_COOKIE);
314         case -2: strerr_die2x(0,INFO,ERR_NOT_ACTIVE);
315         default: doit(listaddr.s,msgnum,when,&bounce);
316     }
317     closesql();
318     _exit(0);
319   }                     /* pre-VERP bounce, in QSBMF format */
320
321   flaghaveheader = 0;
322   flaghaveintro = 0;
323
324   for (;;) {
325     if (!stralloc_copys(&paragraph,"")) die_nomem();
326     for (;;) {
327       if (getln(&ssin,&line,&match,'\n') == -1)
328         strerr_die2sys(111,FATAL,ERR_READ_INPUT);
329       if (!match) die_trash();
330       if (!stralloc_cat(&paragraph,&line)) die_nomem();
331       if (line.len <= 1) break;
332     }
333
334     if (!flaghaveheader) {
335       if (!stralloc_copy(&header,&paragraph)) die_nomem();
336       flaghaveheader = 1;
337       continue;
338     }
339
340     if (!flaghaveintro) {
341       if (paragraph.s[0] == '-' && paragraph.s[1] == '-')
342         continue;               /* skip MIME boundary if it exists */
343       if (paragraph.len < 15) die_trash();
344       if (str_diffn(paragraph.s,"Hi. This is the",15)) die_trash();
345       if (!stralloc_copy(&intro,&paragraph)) die_nomem();
346       flaghaveintro = 1;
347       continue;
348     }
349
350     if (paragraph.s[0] == '-')
351       break;
352
353     if (paragraph.s[0] == '<') {        /* find address */
354       if (!stralloc_copy(&failure,&paragraph)) die_nomem();
355
356       if (!stralloc_copy(&bounce,&header)) die_nomem();
357       if (!stralloc_cat(&bounce,&intro)) die_nomem();
358       if (!stralloc_cat(&bounce,&failure)) die_nomem();
359
360       i = byte_chr(failure.s,failure.len,'\n');
361       if (i < 3) die_trash();
362
363       if (!stralloc_copyb(&listaddr,failure.s + 1,i - 3)) die_nomem();
364       if (byte_chr(listaddr.s,listaddr.len,'\0') == listaddr.len) {
365         if (!stralloc_0(&listaddr)) die_nomem();
366         if (subreceipt(workdir,msgnum,&tagline,listaddr.s,-1,INFO,FATAL) == 0)
367           doit(listaddr.s,msgnum,when,&bounce);
368       }
369     }
370   }
371   closesql();
372   _exit(0);
373 }
374