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 */