Commit | Line | Data |
---|---|---|
f8beb284 MW |
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("ed,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(¶graph,"")) 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(¶graph,&line)) die_nomem(); | |
331 | if (line.len <= 1) break; | |
332 | } | |
333 | ||
334 | if (!flaghaveheader) { | |
335 | if (!stralloc_copy(&header,¶graph)) 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,¶graph)) 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,¶graph)) 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 |