Import ezmlm-idx 0.40
[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