Import ezmlm-idx 0.40
[ezmlm] / ezmlm-warn.c
1 /*$Id: ezmlm-warn.c,v 1.27 1999/08/07 20:47:26 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include "direntry.h"
6 #include "readwrite.h"
7 #include "getln.h"
8 #include "substdio.h"
9 #include "stralloc.h"
10 #include "slurp.h"
11 #include "sgetopt.h"
12 #include "getconf.h"
13 #include "byte.h"
14 #include "error.h"
15 #include "str.h"
16 #include "strerr.h"
17 #include "sig.h"
18 #include "now.h"
19 #include "datetime.h"
20 #include "date822fmt.h"
21 #include "fmt.h"
22 #include "cookie.h"
23 #include "qmail.h"
24 #include "errtxt.h"
25 #include "mime.h"
26 #include "idx.h"
27 #include "subscribe.h"
28
29 #define FATAL "ezmlm-warn: fatal: "
30 void die_usage()
31 {
32 strerr_die1x(100,"ezmlm-warn: usage: ezmlm-warn -dD -l secs -t days dir");
33 }
34
35 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
36
37 stralloc key = {0};
38 stralloc outhost = {0};
39 stralloc outlocal = {0};
40 stralloc mailinglist = {0};
41 stralloc digdir = {0};
42 stralloc charset = {0};
43 char boundary[COOKIE];
44
45 substdio ssout;
46 char outbuf[16];
47
48 unsigned long when;
49 char *dir;
50 char *workdir;
51 int flagdig = 0;
52 char flagcd = '\0'; /* default: don't use transfer encoding */
53 stralloc fn = {0};
54 stralloc bdname = {0};
55 stralloc fnlasth = {0};
56 stralloc fnlastd = {0};
57 stralloc lasth = {0};
58 stralloc lastd = {0};
59 struct stat st;
60 void *psql = (void *) 0;
61
62 void die_read() { strerr_die4sys(111,FATAL,ERR_READ,fn.s,": "); }
63
64 void makedir(s)
65 char *s;
66 {
67 if (mkdir(s,0755) == -1)
68 if (errno != error_exist)
69 strerr_die4x(111,FATAL,ERR_CREATE,s,": ");
70 }
71
72 char inbuf[1024];
73 substdio ssin;
74 char textbuf[1024];
75 substdio sstext;
76
77 stralloc addr = {0};
78 char strnum[FMT_ULONG];
79 char hash[COOKIE];
80 stralloc fnhash = {0};
81 stralloc quoted = {0};
82 stralloc line = {0};
83 stralloc qline = {0};
84
85 struct qmail qq;
86 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
87 {
88 qmail_put(&qq,buf,len);
89 return len;
90 }
91 char qqbuf[1];
92 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
93 struct datetime dt;
94 char date[DATE822FMT];
95
96 void code_qput(s,n)
97 char *s;
98 unsigned int n;
99 {
100 if (!flagcd)
101 qmail_put(&qq,s,n);
102 else {
103 if (flagcd == 'B')
104 encodeB(s,n,&qline,0,FATAL);
105 else
106 encodeQ(s,n,&qline,FATAL);
107 qmail_put(&qq,qline.s,qline.len);
108 }
109 }
110
111 void doit(flagw)
112 int flagw;
113 {
114 unsigned int i;
115 int fd;
116 int match;
117 int fdhash;
118 char *err;
119 datetime_sec msgwhen;
120
121 fd = open_read(fn.s);
122 if (fd == -1) die_read();
123 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
124
125 if (getln(&ssin,&addr,&match,'\0') == -1) die_read();
126 if (!match) { close(fd); return; }
127 if (!issub(workdir,addr.s,(char *) 0,FATAL)) { close(fd);
128 /*XXX*/unlink(fn.s); return; }
129 cookie(hash,"",0,"",addr.s,"");
130 if (!stralloc_copys(&fnhash,workdir)) die_nomem();
131 if (!stralloc_cats(&fnhash,"/bounce/h/")) die_nomem();
132 if (!stralloc_catb(&fnhash,hash,1)) die_nomem();
133 if (!stralloc_cats(&fnhash,"/h")) die_nomem();
134 if (!stralloc_catb(&fnhash,hash+1,COOKIE-1)) die_nomem();
135 if (!stralloc_0(&fnhash)) die_nomem();
136
137 if (qmail_open(&qq, (stralloc *) 0) == -1)
138 strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
139
140 msgwhen = now();
141 qmail_puts(&qq,"Mailing-List: ");
142 qmail_put(&qq,mailinglist.s,mailinglist.len);
143 if (getconf_line(&line,"listid",0,FATAL,dir)) {
144 qmail_puts(&qq,"\nList-ID: ");
145 qmail_put(&qq,line.s,line.len);
146 }
147 qmail_puts(&qq,"\nDate: ");
148 datetime_tai(&dt,msgwhen);
149 qmail_put(&qq,date,date822fmt(date,&dt));
150 if (!stralloc_copys(&line,"Message-ID: <")) die_nomem();
151 if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,(unsigned long) msgwhen)))
152 die_nomem();
153 if (!stralloc_cats(&line,".")) die_nomem();
154 if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,(unsigned long) getpid())))
155 die_nomem();
156 if (!stralloc_cats(&line,".ezmlm-warn@")) die_nomem();
157 if (!stralloc_catb(&line,outhost.s,outhost.len)) die_nomem();
158 qmail_put(&qq,line.s,line.len);
159 if (flagcd) {
160 if (!stralloc_0(&line)) die_nomem();
161 cookie(boundary,"",0,"",line.s,""); /* universal MIME boundary */
162 }
163 qmail_puts(&qq,">\nFrom: ");
164 if (!quote(&quoted,&outlocal)) die_nomem();
165 qmail_put(&qq,quoted.s,quoted.len);
166 qmail_puts(&qq,"-help@");
167 qmail_put(&qq,outhost.s,outhost.len);
168 qmail_puts(&qq,"\nTo: ");
169 if (!quote2(&quoted,addr.s)) die_nomem();
170 qmail_put(&qq,quoted.s,quoted.len);
171 if (flagcd) { /* to accomodate transfer-encoding */
172 qmail_puts(&qq,"\nMIME-Version: 1.0\n");
173 qmail_puts(&qq,"Content-Type: multipart/mixed; boundary=");
174 qmail_put(&qq,boundary,COOKIE);
175 } else {
176 qmail_puts(&qq,"\nContent-type: text/plain; charset=");
177 qmail_puts(&qq,charset.s);
178 }
179 qmail_puts(&qq,flagw ? "\nSubject: ezmlm probe\n" : "\nSubject: ezmlm warning\n");
180
181 if (flagcd) { /* first part for QP/base64 multipart msg */
182 qmail_puts(&qq,"\n\n--");
183 qmail_put(&qq,boundary,COOKIE);
184 qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
185 qmail_puts(&qq,charset.s);
186 qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
187 if (flagcd == 'Q')
188 qmail_puts(&qq,"Quoted-printable\n\n");
189 else
190 qmail_puts(&qq,"base64\n\n");
191 } else
192 qmail_puts(&qq,"\n");
193
194 copy(&qq,"text/top",flagcd,FATAL);
195 copy(&qq,flagw ? "text/bounce-probe" : "text/bounce-warn",flagcd,FATAL);
196
197 if (!flagw) {
198 if (flagdig)
199 copy(&qq,"text/dig-bounce-num",flagcd,FATAL);
200 else
201 copy(&qq,"text/bounce-num",flagcd,FATAL);
202 if (!flagcd) {
203 fdhash = open_read(fnhash.s);
204 if (fdhash == -1) {
205 if (errno != error_noent)
206 strerr_die4sys(111,FATAL,ERR_OPEN,fnhash.s,": ");
207 } else {
208 substdio_fdbuf(&sstext,read,fdhash,textbuf,sizeof(textbuf));
209 for(;;) {
210 if (getln(&sstext,&line,&match,'\n') == -1)
211 strerr_die4sys(111,FATAL,ERR_READ,fnhash.s,": ");
212 if (!match) break;
213 code_qput(line.s,line.len);
214 }
215 }
216 close(fdhash);
217 } else {
218 if (!stralloc_copys(&line,"")) die_nomem(); /* slurp adds! */
219 if (slurp(fnhash.s,&line,256) < 0)
220 strerr_die4sys(111,FATAL,ERR_OPEN,fnhash.s,": ");
221 code_qput(line.s,line.len);
222 }
223 }
224
225 copy(&qq,"text/bounce-bottom",flagcd,FATAL);
226 if (flagcd) {
227 if (flagcd == 'B') {
228 encodeB("",0,&line,2,FATAL);
229 qmail_put(&qq,line.s,line.len); /* flush */
230 }
231 qmail_puts(&qq,"\n\n--");
232 qmail_put(&qq,boundary,COOKIE);
233 qmail_puts(&qq,"\nContent-Type: message/rfc822\n\n");
234 }
235 if (substdio_copy(&ssqq,&ssin) < 0) die_read();
236 close(fd);
237
238 if (flagcd) { /* end multipart/mixed */
239 qmail_puts(&qq,"\n--");
240 qmail_put(&qq,boundary,COOKIE);
241 qmail_puts(&qq,"--\n");
242 }
243
244 strnum[fmt_ulong(strnum,when)] = 0;
245 cookie(hash,key.s,key.len,strnum,addr.s,flagw ? "P" : "W");
246 if (!stralloc_copy(&line,&outlocal)) die_nomem();
247 if (!stralloc_cats(&line,flagw ? "-return-probe-" : "-return-warn-"))
248 die_nomem();
249 if (!stralloc_cats(&line,strnum)) die_nomem();
250 if (!stralloc_cats(&line,".")) die_nomem();
251 if (!stralloc_catb(&line,hash,COOKIE)) die_nomem();
252 if (!stralloc_cats(&line,"-")) die_nomem();
253 i = str_chr(addr.s,'@');
254 if (!stralloc_catb(&line,addr.s,i)) die_nomem();
255 if (addr.s[i]) {
256 if (!stralloc_cats(&line,"=")) die_nomem();
257 if (!stralloc_cats(&line,addr.s + i + 1)) die_nomem();
258 }
259 if (!stralloc_cats(&line,"@")) die_nomem();
260 if (!stralloc_cat(&line,&outhost)) die_nomem();
261 if (!stralloc_0(&line)) die_nomem();
262 qmail_from(&qq,line.s);
263
264 qmail_to(&qq,addr.s);
265 if (*(err = qmail_close(&qq)) != '\0')
266 strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE, err + 1);
267
268 strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
269 strerr_warn2("ezmlm-warn: info: qp ",strnum,0);
270
271 if (!flagw) {
272 if (unlink(fnhash.s) == -1)
273 if (errno != error_noent)
274 strerr_die4sys(111,FATAL,ERR_DELETE,fnhash.s,": ");
275 }
276 if (unlink(fn.s) == -1)
277 strerr_die4sys(111,FATAL,ERR_DELETE,fn.s,": ");
278 }
279
280 void main(argc,argv)
281 int argc;
282 char **argv;
283 {
284 DIR *bouncedir, *bsdir, *hdir;
285 direntry *d, *ds;
286 unsigned long bouncedate;
287 unsigned long bouncetimeout = BOUNCE_TIMEOUT;
288 unsigned long lockout = 0L;
289 unsigned long ld;
290 unsigned long ddir,dfile;
291 int fdlock,fd;
292 char *err;
293 int opt;
294 char ch;
295
296 (void) umask(022);
297 sig_pipeignore();
298 when = (unsigned long) now();
299 while ((opt = getopt(argc,argv,"dDl:t:vV")) != opteof)
300 switch(opt) {
301 case 'd': flagdig = 1; break;
302 case 'D': flagdig = 0; break;
303 case 'l':
304 if (optarg) { /* lockout in seconds */
305 (void) scan_ulong(optarg,&lockout);
306 }
307 break;
308 case 't':
309 if (optarg) { /* bouncetimeout in days */
310 (void) scan_ulong(optarg,&bouncetimeout);
311 bouncetimeout *= 3600L * 24L;
312 }
313 break;
314 case 'v':
315 case 'V': strerr_die2x(0,
316 "ezmlm-warn version: ezmlm-0.53+",EZIDX_VERSION);
317 default:
318 die_usage();
319 }
320 dir = argv[optind];
321 if (!dir) die_usage();
322 if (chdir(dir) == -1)
323 strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
324 if (flagdig) {
325 if (!stralloc_copys(&digdir,dir)) die_nomem();
326 if (!stralloc_cats(&digdir,"/digest")) die_nomem();
327 if (!stralloc_0(&digdir)) die_nomem();
328 workdir = digdir.s;
329 } else
330 workdir = dir;
331
332 if (!stralloc_copys(&fnlastd,workdir)) die_nomem();
333 if (!stralloc_cats(&fnlastd,"/bounce/lastd")) die_nomem();
334 if (!stralloc_0(&fnlastd)) die_nomem();
335 if (slurp(fnlastd.s,&lastd,16) == -1) /* last time d was scanned */
336 strerr_die4sys(111,FATAL,ERR_READ,fnlastd.s,": ");
337 if (!stralloc_0(&lastd)) die_nomem();
338 (void) scan_ulong(lastd.s,&ld);
339 if (!lockout)
340 lockout = bouncetimeout / 50; /* 5.6 h for default timeout */
341 if (ld + lockout > when && ld < when)
342 _exit(0); /* exit silently. Second check is to prevent lockup */
343 /* if lastd gets corrupted */
344
345 if (!stralloc_copy(&fnlasth,&fnlastd)) die_nomem();
346 fnlasth.s[fnlasth.len - 2] = 'h'; /* bad, but feels good ... */
347
348 switch(slurp("key",&key,32)) {
349 case -1:
350 strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
351 case 0:
352 strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
353 }
354 getconf_line(&outhost,"outhost",1,FATAL,dir);
355 getconf_line(&outlocal,"outlocal",1,FATAL,dir);
356 if (flagdig)
357 if (!stralloc_cats(&outlocal,"-digest")) die_nomem();
358 getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
359 if (getconf_line(&charset,"charset",0,FATAL,dir)) {
360 if (charset.len >= 2 && charset.s[charset.len - 2] == ':') {
361 if (charset.s[charset.len - 1] == 'B' ||
362 charset.s[charset.len - 1] == 'Q') {
363 flagcd = charset.s[charset.len - 1];
364 charset.s[charset.len - 2] = '\0';
365 }
366 }
367 } else
368 if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
369 if (!stralloc_0(&charset)) die_nomem();
370
371 set_cpoutlocal(&outlocal); /* for copy */
372 set_cpouthost(&outhost); /* for copy */
373 ddir = when / 10000;
374 dfile = when - 10000 * ddir;
375
376 if (!stralloc_copys(&line,workdir)) die_nomem();
377 if (!stralloc_cats(&line,"/lockbounce")) die_nomem();
378 if (!stralloc_0(&line)) die_nomem();
379 fdlock = open_append(line.s);
380 if (fdlock == -1)
381 strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
382 if (lock_ex(fdlock) == -1)
383 strerr_die4sys(111,FATAL,ERR_OBTAIN,line.s,": ");
384
385 if (!stralloc_copys(&line,workdir)) die_nomem();
386 if (!stralloc_cats(&line,"/bounce/d")) die_nomem();
387 if (!stralloc_0(&line)) die_nomem();
388 bouncedir = opendir(line.s);
389 if (!bouncedir)
390 if (errno != error_noent)
391 strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
392 else
393 _exit(0); /* no bouncedir - no bounces! */
394
395 while ((d = readdir(bouncedir))) { /* dxxx/ */
396 if (str_equal(d->d_name,".")) continue;
397 if (str_equal(d->d_name,"..")) continue;
398
399 scan_ulong(d->d_name,&bouncedate);
400 /* since we do entire dir, we do files that are not old enough. */
401 /* to not do this and accept a delay of 10000s (2.8h) of the oldest */
402 /* bounce we add to bouncedate. We don't if bouncetimeout=0 so that */
403 /* that setting still processes _all_ bounces. */
404 if (bouncetimeout) ++bouncedate;
405 if (when >= bouncedate * 10000 + bouncetimeout) {
406 if (!stralloc_copys(&bdname,workdir)) die_nomem();
407 if (!stralloc_cats(&bdname,"/bounce/d/")) die_nomem();
408 if (!stralloc_cats(&bdname,d->d_name)) die_nomem();
409 if (!stralloc_0(&bdname)) die_nomem();
410 bsdir = opendir(bdname.s);
411 if (!bsdir) {
412 if (errno != error_notdir)
413 strerr_die4sys(111,FATAL,ERR_OPEN,bdname.s,":y ");
414 else { /* leftover nnnnn_dmmmmm file */
415 if (unlink(bdname.s) == -1)
416 strerr_die4sys(111,FATAL,ERR_DELETE,bdname.s,": ");
417 continue;
418 }
419 }
420 while ((ds = readdir(bsdir))) { /* dxxxx/yyyy */
421 if (str_equal(ds->d_name,".")) continue;
422 if (str_equal(ds->d_name,"..")) continue;
423 if (!stralloc_copy(&fn,&bdname)) die_nomem(); /* '\0' at end */
424 fn.s[fn.len - 1] = '/';
425 if (!stralloc_cats(&fn,ds->d_name)) die_nomem();
426 if (!stralloc_0(&fn)) die_nomem();
427 if ((ds->d_name[0] == 'd') || (ds->d_name[0] == 'w'))
428 doit(ds->d_name[0] == 'w');
429 else /* other stuff is junk */
430 if (unlink(fn.s) == -1)
431 strerr_die4sys(111,FATAL,ERR_DELETE,fn.s,": ");
432 }
433 closedir(bsdir);
434 if (rmdir(bdname.s) == -1) /* the directory itself */
435 if (errno != error_noent)
436 strerr_die4sys(111,FATAL,ERR_DELETE,bdname.s,": ");
437 }
438 }
439 closedir(bouncedir);
440
441 if (!stralloc_copy(&line,&fnlastd)) die_nomem();
442 line.s[line.len - 2] = 'D';
443 fd = open_trunc(line.s); /* write lastd. Do safe */
444 /* since we read before lock*/
445 if (fd == -1) strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
446 substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
447 if (substdio_put(&ssout,strnum,fmt_ulong(strnum,when)) == -1)
448 strerr_die4sys(111,FATAL,ERR_WRITE,line.s,": ");
449 if (substdio_put(&ssout,"\n",1) == -1) /* prettier */
450 strerr_die4sys(111,FATAL,ERR_WRITE,line.s,": ");
451 if (substdio_flush(&ssout) == -1)
452 strerr_die4sys(111,FATAL,ERR_FLUSH,line.s,": ");
453 if (fsync(fd) == -1)
454 strerr_die4sys(111,FATAL,ERR_SYNC,line.s,": ");
455 if (close(fd) == -1)
456 strerr_die4sys(111,FATAL,ERR_CLOSE,line.s,": ");
457
458 if (rename(line.s,fnlastd.s) == -1)
459 strerr_die4sys(111,FATAL,ERR_MOVE,fnlastd.s,": ");
460
461 /* no need to do h dir cleaning more than */
462 /* once per 1-2 days (17-30 days for all) */
463 if (stat(fnlasth.s,&st) == -1) {
464 if (errno != error_noent)
465 strerr_die4sys(111,FATAL,ERR_STAT,fnlasth.s,": ");
466 } else if (when < st.st_mtime + 100000 && when > st.st_mtime)
467 _exit(0); /* 2nd comp to guard against corruption */
468
469 if (slurp(fnlasth.s,&lasth,16) == -1) /* last h cleaned */
470 strerr_die4sys(111,FATAL,ERR_READ,fnlasth.s,": ");
471 if (!stralloc_0(&lasth)) die_nomem();
472 ch = lasth.s[0]; /* clean h */
473 if (ch >= 'a' && ch <= 'o')
474 ++ch;
475 else
476 ch = 'a';
477 lasth.s[0] = ch;
478 if (!stralloc_copys(&line,workdir)) die_nomem();
479 if (!stralloc_cats(&line,"/bounce/h/")) die_nomem();
480 if (!stralloc_catb(&line,lasth.s,1)) die_nomem();
481 if (!stralloc_0(&line)) die_nomem();
482 hdir = opendir(line.s); /* clean ./h/xxxxxx */
483
484 if (!hdir) {
485 if (errno != error_noent)
486 strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
487 } else {
488
489 while ((d = readdir(hdir))) {
490 if (str_equal(d->d_name,".")) continue;
491 if (str_equal(d->d_name,"..")) continue;
492 if (!stralloc_copys(&fn,line.s)) die_nomem();
493 if (!stralloc_append(&fn,"/")) die_nomem();
494 if (!stralloc_cats(&fn,d->d_name)) die_nomem();
495 if (!stralloc_0(&fn)) die_nomem();
496 if (stat(fn.s,&st) == -1) {
497 if (errno == error_noent) continue;
498 strerr_die4sys(111,FATAL,ERR_STAT,fn.s,": ");
499 }
500 if (when > st.st_mtime + 3 * bouncetimeout)
501 if (unlink(fn.s) == -1)
502 strerr_die4sys(111,FATAL,ERR_DELETE,fn.s,": ");
503 }
504 closedir(hdir);
505 }
506
507 fd = open_trunc(fnlasth.s); /* write lasth */
508 if (fd == -1) strerr_die4sys(111,FATAL,ERR_OPEN,fnlasth.s,": ");
509 substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
510 if (substdio_put(&ssout,lasth.s,1) == -1)
511 strerr_die4sys(111,FATAL,ERR_OPEN,fnlasth.s,": ");
512 if (substdio_put(&ssout,"\n",1) == -1) /* prettier */
513 strerr_die4sys(111,FATAL,ERR_OPEN,fnlasth.s,": ");
514 if (substdio_flush(&ssout) == -1)
515 strerr_die4sys(111,FATAL,ERR_OPEN,fnlasth.s,": ");
516 (void) close(fd); /* no big loss. No reason to flush/sync */
517 /* See check of ld above to guard against */
518 /* it being corrupted and > when */
519
520 closesql();
521 _exit(0);
522 }