1 /*$Id: ezmlm-warn.c,v 1.27 1999/08/07 20:47:26 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
20 #include "date822fmt.h"
27 #include "subscribe.h"
29 #define FATAL "ezmlm-warn: fatal: "
32 strerr_die1x(100,"ezmlm-warn: usage: ezmlm-warn -dD -l secs -t days dir");
35 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
38 stralloc outhost = {0};
39 stralloc outlocal = {0};
40 stralloc mailinglist = {0};
41 stralloc digdir = {0};
42 stralloc charset = {0};
43 char boundary[COOKIE];
52 char flagcd = '\0'; /* default: don't use transfer encoding */
54 stralloc bdname = {0};
55 stralloc fnlasth = {0};
56 stralloc fnlastd = {0};
60 void *psql = (void *) 0;
62 void die_read() { strerr_die4sys(111,FATAL,ERR_READ,fn.s,": "); }
67 if (mkdir(s,0755) == -1)
68 if (errno != error_exist)
69 strerr_die4x(111,FATAL,ERR_CREATE,s,": ");
78 char strnum[FMT_ULONG];
80 stralloc fnhash = {0};
81 stralloc quoted = {0};
86 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
88 qmail_put(&qq,buf,len);
92 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
94 char date[DATE822FMT];
104 encodeB(s,n,&qline,0,FATAL);
106 encodeQ(s,n,&qline,FATAL);
107 qmail_put(&qq,qline.s,qline.len);
119 datetime_sec msgwhen;
121 fd = open_read(fn.s);
122 if (fd == -1) die_read();
123 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
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();
137 if (qmail_open(&qq, (stralloc *) 0) == -1)
138 strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
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);
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)))
153 if (!stralloc_cats(&line,".")) die_nomem();
154 if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,(unsigned long) getpid())))
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);
160 if (!stralloc_0(&line)) die_nomem();
161 cookie(boundary,"",0,"",line.s,""); /* universal MIME boundary */
163 qmail_puts(&qq,">\nFrom: ");
164 if (!quote("ed,&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("ed,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);
176 qmail_puts(&qq,"\nContent-type: text/plain; charset=");
177 qmail_puts(&qq,charset.s);
179 qmail_puts(&qq,flagw ? "\nSubject: ezmlm probe\n" : "\nSubject: ezmlm warning\n");
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: ");
188 qmail_puts(&qq,"Quoted-printable\n\n");
190 qmail_puts(&qq,"base64\n\n");
192 qmail_puts(&qq,"\n");
194 copy(&qq,"text/top",flagcd,FATAL);
195 copy(&qq,flagw ? "text/bounce-probe" : "text/bounce-warn",flagcd,FATAL);
199 copy(&qq,"text/dig-bounce-num",flagcd,FATAL);
201 copy(&qq,"text/bounce-num",flagcd,FATAL);
203 fdhash = open_read(fnhash.s);
205 if (errno != error_noent)
206 strerr_die4sys(111,FATAL,ERR_OPEN,fnhash.s,": ");
208 substdio_fdbuf(&sstext,read,fdhash,textbuf,sizeof(textbuf));
210 if (getln(&sstext,&line,&match,'\n') == -1)
211 strerr_die4sys(111,FATAL,ERR_READ,fnhash.s,": ");
213 code_qput(line.s,line.len);
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);
225 copy(&qq,"text/bounce-bottom",flagcd,FATAL);
228 encodeB("",0,&line,2,FATAL);
229 qmail_put(&qq,line.s,line.len); /* flush */
231 qmail_puts(&qq,"\n\n--");
232 qmail_put(&qq,boundary,COOKIE);
233 qmail_puts(&qq,"\nContent-Type: message/rfc822\n\n");
235 if (substdio_copy(&ssqq,&ssin) < 0) die_read();
238 if (flagcd) { /* end multipart/mixed */
239 qmail_puts(&qq,"\n--");
240 qmail_put(&qq,boundary,COOKIE);
241 qmail_puts(&qq,"--\n");
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-"))
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();
256 if (!stralloc_cats(&line,"=")) die_nomem();
257 if (!stralloc_cats(&line,addr.s + i + 1)) die_nomem();
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);
264 qmail_to(&qq,addr.s);
265 if (*(err = qmail_close(&qq)) != '\0')
266 strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE, err + 1);
268 strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
269 strerr_warn2("ezmlm-warn: info: qp ",strnum,0);
272 if (unlink(fnhash.s) == -1)
273 if (errno != error_noent)
274 strerr_die4sys(111,FATAL,ERR_DELETE,fnhash.s,": ");
276 if (unlink(fn.s) == -1)
277 strerr_die4sys(111,FATAL,ERR_DELETE,fn.s,": ");
284 DIR *bouncedir, *bsdir, *hdir;
286 unsigned long bouncedate;
287 unsigned long bouncetimeout = BOUNCE_TIMEOUT;
288 unsigned long lockout = 0L;
290 unsigned long ddir,dfile;
298 when = (unsigned long) now();
299 while ((opt = getopt(argc,argv,"dDl:t:vV")) != opteof)
301 case 'd': flagdig = 1; break;
302 case 'D': flagdig = 0; break;
304 if (optarg) { /* lockout in seconds */
305 (void) scan_ulong(optarg,&lockout);
309 if (optarg) { /* bouncetimeout in days */
310 (void) scan_ulong(optarg,&bouncetimeout);
311 bouncetimeout *= 3600L * 24L;
315 case 'V': strerr_die2x(0,
316 "ezmlm-warn version: ezmlm-0.53+",EZIDX_VERSION);
321 if (!dir) die_usage();
322 if (chdir(dir) == -1)
323 strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
325 if (!stralloc_copys(&digdir,dir)) die_nomem();
326 if (!stralloc_cats(&digdir,"/digest")) die_nomem();
327 if (!stralloc_0(&digdir)) die_nomem();
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);
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 */
345 if (!stralloc_copy(&fnlasth,&fnlastd)) die_nomem();
346 fnlasth.s[fnlasth.len - 2] = 'h'; /* bad, but feels good ... */
348 switch(slurp("key",&key,32)) {
350 strerr_die4sys(111,FATAL,ERR_READ,dir,"/key: ");
352 strerr_die4x(100,FATAL,dir,"/key",ERR_NOEXIST);
354 getconf_line(&outhost,"outhost",1,FATAL,dir);
355 getconf_line(&outlocal,"outlocal",1,FATAL,dir);
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';
368 if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
369 if (!stralloc_0(&charset)) die_nomem();
371 set_cpoutlocal(&outlocal); /* for copy */
372 set_cpouthost(&outhost); /* for copy */
374 dfile = when - 10000 * ddir;
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);
381 strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
382 if (lock_ex(fdlock) == -1)
383 strerr_die4sys(111,FATAL,ERR_OBTAIN,line.s,": ");
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);
390 if (errno != error_noent)
391 strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
393 _exit(0); /* no bouncedir - no bounces! */
395 while ((d = readdir(bouncedir))) { /* dxxx/ */
396 if (str_equal(d->d_name,".")) continue;
397 if (str_equal(d->d_name,"..")) continue;
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);
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,": ");
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,": ");
434 if (rmdir(bdname.s) == -1) /* the directory itself */
435 if (errno != error_noent)
436 strerr_die4sys(111,FATAL,ERR_DELETE,bdname.s,": ");
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,": ");
454 strerr_die4sys(111,FATAL,ERR_SYNC,line.s,": ");
456 strerr_die4sys(111,FATAL,ERR_CLOSE,line.s,": ");
458 if (rename(line.s,fnlastd.s) == -1)
459 strerr_die4sys(111,FATAL,ERR_MOVE,fnlastd.s,": ");
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 */
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')
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 */
485 if (errno != error_noent)
486 strerr_die4sys(111,FATAL,ERR_OPEN,line.s,": ");
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,": ");
500 if (when > st.st_mtime + 3 * bouncetimeout)
501 if (unlink(fn.s) == -1)
502 strerr_die4sys(111,FATAL,ERR_DELETE,fn.s,": ");
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 */