Import ezmlm-idx 0.40
[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