Commit | Line | Data |
---|---|---|
f8beb284 MW |
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("ed,&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 |