chiark / gitweb /
Debianization and various other fixes.
[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 }