chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-clean.c
1 /*$Id: ezmlm-clean.c,v 1.30 1999/05/12 22:15:26 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include "error.h"
6 #include "stralloc.h"
7 #include "str.h"
8 #include "env.h"
9 #include "sig.h"
10 #include "slurp.h"
11 #include "getconf.h"
12 #include "strerr.h"
13 #include "byte.h"
14 #include "getln.h"
15 #include "case.h"
16 #include "qmail.h"
17 #include "substdio.h"
18 #include "readwrite.h"
19 #include "seek.h"
20 #include "quote.h"
21 #include "datetime.h"
22 #include "now.h"
23 #include "date822fmt.h"
24 #include "direntry.h"
25 #include "cookie.h"
26 #include "sgetopt.h"
27 #include "fmt.h"
28 #include "errtxt.h"
29 #include "copy.h"
30 #include "idx.h"
31 #include "mime.h"
32
33 int flagmime = MOD_MIME;        /* default is message as attachment */
34 int flagreturn = 1;             /* default return timed-out messages */
35 char flagcd = '\0';             /* default: no transferencoding */
36 stralloc fnmsg = {0};
37
38 /* When ezmlm-clean is run, messages and message stubs in pending/      */
39 /* rejected/accepted are erased if they are older than delay hours.     */
40 /* Timeouts in h for messages. If modtime has a number, it is made to be*/
41 /* in the range DELAY_MIN..DELAY_MAX. If the number is 0 or there is no */
42 /* number, DELAY_DEFAULT is used. Messages that are read-only are       */
43 /* ignored. Messages in 'pending' that have the execute bit set result  */
44 /* in an informative reply to the poster. Any defects in the message    */
45 /* format, inability to open the file, etc, result in a maillog entry   */
46 /* whereafter the message is erased. */
47
48 /* The defines are in "idx.h" */
49
50 #define FATAL "ezmlm-clean: fatal: "
51
52 void die_read()
53 {
54   strerr_die4x(111,FATAL,ERR_READ,fnmsg.s,": ");
55 }
56
57 void die_usage()
58 {
59   strerr_die1x(100,"ezmlm-clean: usage: ezmlm-clean [-mMrRvV] dir");
60 }
61
62 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
63
64 datetime_sec when;
65 unsigned int older;
66 struct datetime dt;
67
68 char textbuf[1024];
69 substdio sstext;
70
71 struct qmail qq;
72 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
73 {
74   qmail_put(&qq,buf,len);
75   return len;
76 }
77 char qqbuf[1];
78 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
79
80 char *dir;
81 char strnum[FMT_ULONG];
82 char date[DATE822FMT];
83 char boundary[COOKIE];
84 datetime_sec hashdate;
85
86 stralloc outhost = {0};
87 stralloc outlocal = {0};
88 stralloc mailinglist = {0};
89 stralloc listid = {0};
90 stralloc quoted = {0};
91 stralloc line = {0};
92 stralloc modtime = {0};
93 stralloc to = {0};
94 stralloc charset = {0};
95
96 int flagconf;
97 int fd;
98 int match;
99 unsigned long msgnum = 0;
100                         /* counter to make message-id unique, since we may */
101                         /* send out several msgs. This is not bullet-proof.*/
102                         /* Duplication occurs if we do x>1 msg && another  */
103                         /* ezmlm started within x seconds, and with the    */
104                         /* same pid. Very unlikely.                        */
105
106 void transferenc()
107 {
108         if (flagcd) {
109           qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
110           if (flagcd == 'Q')
111             qmail_puts(&qq,"Quoted-Printable\n\n");
112           else
113             qmail_puts(&qq,"base64\n\n");
114         } else
115           qmail_puts(&qq,"\n\n");
116 }
117 void readconfigs()
118 /* gets outlocal, outhost, etc. This is done only if there are any timed-out*/
119 /* messages found, that merit a reply to the author. */
120 {
121
122   getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
123   getconf_line(&listid,"listid",0,FATAL,dir);
124   getconf_line(&outhost,"outhost",1,FATAL,dir);
125   getconf_line(&outlocal,"outlocal",1,FATAL,dir);
126   set_cpouthost(&outlocal);
127   set_cpoutlocal(&outlocal);
128 }
129
130 void sendnotice(d)
131 char *d;
132 /* sends file pointed to by d to the address in the return-path of the  */
133 /* message. */
134 {
135   unsigned int x,y;
136   char *err;
137
138       if (!flagconf) {
139         readconfigs();
140       }
141       if (qmail_open(&qq, (stralloc *) 0) == -1)
142         strerr_die2x(111,FATAL,ERR_QMAIL_QUEUE);
143
144       fd = open_read(d);
145       if (fd == -1)
146         strerr_die4sys(111,FATAL,ERR_OPEN,d,": ");
147       substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
148       if (getln(&sstext,&line,&match,'\n') == -1) die_read();
149       if (!match) die_read();
150       if (!case_startb(line.s,line.len,"return-path:")) die_read();
151       x = 12 + byte_chr(line.s + 12,line.len-12,'<');
152       y = byte_rchr(line.s + x,line.len-x,'>');
153       if (x != line.len && x+y != line.len) {
154         if (!stralloc_copyb(&to,line.s+x+1, y-1)) die_nomem();
155         if (!stralloc_0(&to)) die_nomem();
156       } else
157         die_read();
158       qmail_puts(&qq,"Mailing-List: ");
159       qmail_put(&qq,mailinglist.s,mailinglist.len);
160       qmail_puts(&qq,"\nList-ID: ");
161       qmail_put(&qq,listid.s,listid.len);
162       qmail_puts(&qq,"\nDate: ");
163       datetime_tai(&dt,when);
164       qmail_put(&qq,date,date822fmt(date,&dt));
165       qmail_puts(&qq,"Message-ID: <");
166       if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,
167                 (unsigned long) when + msgnum++))) die_nomem();
168       if (!stralloc_append(&line,".")) die_nomem();
169       if (!stralloc_catb(&line,strnum,
170                 fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem();
171       if (!stralloc_cats(&line,".ezmlm@")) die_nomem();
172       if (!stralloc_cat(&line,&outhost)) die_nomem();
173       if (!stralloc_0(&line)) die_nomem();
174       qmail_puts(&qq,line.s);
175                 /* "unique" MIME boundary as hash of messageid */
176       cookie(boundary,"",0,"",line.s,"");
177       qmail_puts(&qq,">\nFrom: ");
178       if (!quote(&quoted,&outlocal)) die_nomem();
179       qmail_put(&qq,quoted.s,quoted.len);
180       qmail_puts(&qq,"-help@");
181       qmail_put(&qq,outhost.s,outhost.len);
182       qmail_puts(&qq,"\nSubject: ");
183       qmail_puts(&qq,TXT_RETURNED_POST);
184       qmail_put(&qq,quoted.s,quoted.len);
185       qmail_puts(&qq,"@");
186       qmail_put(&qq,outhost.s,outhost.len);
187       qmail_puts(&qq, "\nTo: ");
188       qmail_puts(&qq,to.s);
189       if (flagmime) {
190         if (getconf_line(&charset,"charset",0,FATAL,dir)) {
191           if (charset.len >= 2 && charset.s[charset.len - 2] == ':') {
192             if (charset.s[charset.len - 1] == 'B' ||
193                 charset.s[charset.len - 1] == 'Q') {
194               flagcd = charset.s[charset.len - 1];
195               charset.s[charset.len - 2] = '\0';
196             }
197           }
198         } else
199           if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
200         if (!stralloc_0(&charset)) die_nomem();
201         qmail_puts(&qq,"\nMIME-Version: 1.0\n");
202         qmail_puts(&qq,"Content-Type: multipart/mixed;\n\tboundary=");
203         qmail_put(&qq,boundary,COOKIE);
204         qmail_puts(&qq,"\n\n--");
205         qmail_put(&qq,boundary,COOKIE);
206         qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
207         qmail_puts(&qq,charset.s);
208         transferenc();
209       } else
210       qmail_puts(&qq,"\n\n");
211
212       copy(&qq,"text/top",flagcd,FATAL);
213       copy(&qq,"text/mod-timeout",flagcd,FATAL);
214       if (flagcd == 'B') {
215         encodeB("",0,&line,2,FATAL);
216         qmail_put(&qq,line.s,line.len);
217       }
218
219       if (flagmime) {
220         qmail_puts(&qq,"\n--");
221         qmail_put(&qq,boundary,COOKIE);
222         qmail_puts(&qq,"\nContent-Type: message/rfc822\n\n");
223       }
224
225       if (seek_begin(fd) == -1)
226         strerr_die4sys(111,FATAL,ERR_SEEK,d,": ");
227
228       substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
229       if (substdio_copy(&ssqq,&sstext) != 0) die_read();
230       close (fd);
231
232       if (flagmime) {
233         qmail_puts(&qq,"\n--");
234         qmail_put(&qq,boundary,COOKIE);
235         qmail_puts(&qq,"--\n");
236       }
237
238       if (!stralloc_copy(&line,&outlocal)) die_nomem();
239       if (!stralloc_cats(&line,"-return-@")) die_nomem();
240       if (!stralloc_cat(&line,&outhost)) die_nomem();
241       if (!stralloc_0(&line)) die_nomem();
242       qmail_from(&qq,line.s);           /* sender */
243         qmail_to(&qq,to.s);
244
245      if (*(err = qmail_close(&qq)) != '\0')
246        strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE, err + 1);
247
248      strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
249      strerr_warn2("ezmlm-clean: info: qp ",strnum,0);
250 }
251
252 void dodir(dirname,reply)
253 char *dirname; int reply;
254 /* parses file names in directory 'dirname'. Files that are not owner */
255 /* writable (w) are ignored. If the files are older (by name!) than   */
256 /* now-delay, action is taken:                                        */
257 /* If the owner x bit is not set, the file is erased.                 */
258 /* If it is set and reply is not set, the file is erased. If both are */
259 /* set, a notice about the timeout is sent to the poster. If this     */
260 /* fails due to a message-related error (format, etc) the file is     */
261 /* erased even though no notice is sent. For temporary errors (like   */
262 /* out-of-memory) the message is left intact for the next run. If the */
263 /* notice is sent successfully, the file is erased. All this is to    */
264 /* do the best possible without risking a rerun of the .qmail file,   */
265 /* which could result in a redelivery of the action request and a     */
266 /* second (incorrect) reply to the moderator's request.               */
267
268 /* NOTE: ALL non-hidden files in this dir are processed and merci-    */
269 /* lessly deleted. No checks for proper file name. E.g. 'HELLO'       */
270 /* => time 0 => will be deleted on the next ezmlm-clean run.          */
271 {
272   DIR *moddir;
273   direntry *d;
274   unsigned long modtime;
275   struct stat st;
276
277   moddir = opendir(dirname);
278   if (!moddir)
279     strerr_die6sys(0,FATAL,ERR_OPEN,dir,"/",dirname,": ");
280   while ((d = readdir(moddir))) {
281     if (d->d_name[0] == '.') continue;
282     scan_ulong(d->d_name,&modtime);
283     if (modtime < older) {
284       if (!stralloc_copys(&fnmsg,dirname)) die_nomem();
285       if (!stralloc_cats(&fnmsg,d->d_name)) die_nomem();
286       if (!stralloc_0(&fnmsg)) die_nomem();
287       if((stat(fnmsg.s,&st) != -1) && (st.st_mode & 0200)) {
288         if(reply && (st.st_mode & 0100)) {
289                         /* unlink unless there was a TEMPORARY */
290                         /* not message-related error notifying */
291                         /* poster and msg x bit set.  Leave r/o*/
292                         /* messages alone. Non-x bit msg are   */
293                         /* trash. Just unlink, don't notify    */
294           sendnotice(fnmsg.s);
295           unlink(fnmsg.s);
296         } else
297           unlink(fnmsg.s);
298       }
299     }
300   }
301   closedir(moddir);
302 }
303
304
305 void main(argc,argv)
306 int argc;
307 char **argv;
308 {
309   int fdlock;
310   int delay;
311   int opt;
312   (void) umask(022);
313   sig_pipeignore();
314   when = now();
315
316   while ((opt = getopt(argc,argv,"mMrRvV")) != opteof)
317     switch(opt) {
318       case 'm': flagmime = 1; break;
319       case 'M': flagmime = 0; break;
320       case 'r': flagreturn = 1; break;
321       case 'R': flagreturn = 0; break;
322       case 'v':
323       case 'V': strerr_die2x(0,"ezmlm-clean version: ", EZIDX_VERSION);
324                 /* not reached */
325       default:
326         die_usage();
327     }
328
329   dir = argv[optind];
330   if (!dir) die_usage();
331
332   if (chdir(dir) == -1)
333     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
334
335   getconf_line(&modtime,"modtime",0,FATAL,dir);
336   if (!stralloc_0(&modtime)) die_nomem();
337   scan_ulong(modtime.s,&delay);
338   if (!delay) delay = DELAY_DEFAULT;
339   else if (delay < DELAY_MIN) delay = DELAY_MIN;
340   else if (delay > DELAY_MAX) delay = DELAY_MAX;
341   older = (unsigned long) when - 3600L * delay; /* delay is in hours */
342
343   fdlock = open_append("mod/lock");
344   if (fdlock == -1)
345     strerr_die4sys(0,FATAL,ERR_OPEN,dir,"/mod/lock: ");
346   if (lock_ex(fdlock) == -1)
347     strerr_die4sys(0,FATAL,ERR_OBTAIN,dir,"/mod/lock: ");
348
349   flagconf = 0;
350   dodir("mod/pending/",flagreturn);
351   dodir("mod/accepted/",0);
352   dodir("mod/rejected/",0);
353   _exit(0);
354 }
355