Import ezmlm-idx 0.40
[ezmlm] / ezmlm-reject.c
CommitLineData
5b62e993
MW
1#include "strerr.h"
2#include "substdio.h"
3#include "readwrite.h"
4#include "stralloc.h"
5#include "getln.h"
6#include "sgetopt.h"
f8beb284
MW
7#include "getconf.h"
8#include "constmap.h"
9#include "fmt.h"
10#include "qmail.h"
11#include "seek.h"
12#include "scan.h"
13#include "env.h"
14#include "errtxt.h"
15#include "idx.h"
5b62e993 16
f8beb284 17#define FATAL "ezmlm-reject: fatal: "
5b62e993 18
f8beb284
MW
19int flagrejectcommands = 1; /* reject if subject is simple command */
20int flagneedsubject = 1; /* reject if subject is missing */
21int flagtook = 0; /* reject unless listaddress in To: or Cc: */
22int exitquiet = 100; /* reject with error (100) rather than exit */
23 /* quietly (99) if listaddress missing */
24int flagheaderreject = 0; /* don't reject messages with headers from */
25 /* other mailing lists. */
26int flagbody = 0; /* =1 => reject is subject or body starts with*/
27 /* "subscribe" or "unsubscribe" */
28int flagforward = 0; /* =1 => forward commands to list-request */
29int flagparsemime = 0;
5b62e993
MW
30int flaghavesubject = 0;
31int flaghavecommand = 0;
f8beb284
MW
32int flagcheck = 0; /* set after boundary is found in body, */
33 /* until blank line */
5b62e993 34
f8beb284
MW
35stralloc mimeremove = {0};
36stralloc mimereject = {0};
37stralloc headerreject = {0};
38
39struct constmap mimeremovemap;
40struct constmap mimerejectmap;
41struct constmap headerrejectmap;
42
43char strnum[FMT_ULONG];
5b62e993 44char buf0[256];
f8beb284
MW
45substdio ssin = SUBSTDIO_FDBUF(read,0,buf0,(int) sizeof(buf0));
46substdio ssin2 = SUBSTDIO_FDBUF(read,0,buf0,(int) sizeof(buf0));
47
48struct qmail qq;
49int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
50{
51 qmail_put(&qq,buf,len);
52 return len;
53}
54
55char qqbuf[1];
56substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf));
57
5b62e993 58stralloc line = {0};
f8beb284
MW
59stralloc to = {0};
60stralloc outhost = {0};
61stralloc outlocal = {0};
62stralloc content = {0};
63stralloc subject = {0};
64stralloc boundary = {0};
65stralloc precd = {0};
66stralloc mydtline = {0};
67
68void die_nomem()
69{
70 strerr_die2x(100,FATAL,ERR_NOMEM);
71}
72
73void die_usage()
74{
75 strerr_die2x(100,FATAL,"usage: ezmlm-reject [-bBcCfFhHqQsStT] [dir]");
76}
77
78unsigned int findlocal(sa,n)
79 /* n is index of '@' within sa. Returns index to last postition */
80 /* of local, n otherwise. */
81stralloc *sa; /* line */
82unsigned int n;
83{
84 char *first;
85 register char *s;
86 register int level = 0;
87
88 first = sa->s;
89 s = sa->s + n;
90 if (s <= first) return n;
91 while (--s >= first) {
92 switch (*s) {
93 case ' ': case '\t': case '\n': break;
94 case ')':
95 if (--s <= first) return n;
96 if (*s == '\\') break;
97 ++level; ++s;
98 while (level && --s > first) {
99 if (*s == ')') if (*(s-1) != '\\') ++level;
100 if (*s == '(') if (*(s-1) != '\\') --level;
101 }
102 break;
103 case '"':
104 --s;
105 if (s < first) return n;
106 return (unsigned int) (s - first);
107 default:
108 return (unsigned int) (s - first);
109 }
110#include "env.h"
111 }
112}
113
114unsigned int findhost(sa,n)
115 /* s in index to a '@' within sa. Returns index to first pos of */
116 /* host part if there is one, n otherwise. */
117stralloc *sa; /* line */
118unsigned int n;
119{
120 char *last;
121 register char *s;
122 register int level = 0;
123
124 last = sa->s + sa->len - 1;
125 s = sa->s + n;
126 if (s >= last) return n;
127 while (++s <= last) {
128 switch (*s) {
129 case ' ': case '\t': case '\n': break;
130 case '(':
131 ++level;
132 while (level && (++s < last)) {
133 if (*s == ')') --level; if (!level) break;
134 if (*s == '(') ++level;
135 if (*s == '\\') ++s;
136 }
137 break;
138 case '"':
139 while (++s < last) {
140 if (*s == '"') break;
141 if (*s == '\\') ++s;
142 }
143 break;
144 default:
145 return (unsigned int) (s - sa->s);
146 }
147 }
148}
149
150int getto(sa)
151 /* find list address in line. If found, return 1, else return 0. */
152 stralloc *sa;
153{
154 unsigned int pos = 0;
155 unsigned int pos1;
156
157 if (!sa->len) return 0; /* no To: or Cc: line */
158 while ((pos += 1 + byte_chr(sa->s+pos+1,sa->len-pos-1,'@')) != sa->len) {
159 pos1 = findhost(sa,pos);
160 if (pos1 == pos) break;
161 if (pos1 + outhost.len <= sa->len)
162 if (!case_diffb(sa->s+pos1,outhost.len,outhost.s)) { /* got host */
163 pos1 = findlocal(sa,pos);
164 if (pos1 == pos) break;
165 ++pos1; /* avoids 1 x 2 below */
166 if (pos1 >= outlocal.len)
167 if (!case_diffb(sa->s+pos1-outlocal.len,outlocal.len,outlocal.s))
168 return 1; /* got local as well */
169 }
170 }
171 return 0;
172}
5b62e993
MW
173
174void main(argc,argv)
175int argc;
176char **argv;
177{
f8beb284
MW
178 unsigned long maxmsgsize = 0L;
179 unsigned long minmsgsize = 0L;
180 unsigned long msgsize = 0L;
5b62e993 181 int opt;
f8beb284
MW
182 char linetype = ' ';
183 char *cp, *cpstart, *cpafter;
184 char *dir;
185 char *err;
186 char *sender;
187 unsigned int len;
5b62e993
MW
188 int match;
189
f8beb284 190 while ((opt = getopt(argc,argv,"bBcCfFhHqQsStT")) != opteof)
5b62e993 191 switch(opt) {
f8beb284
MW
192 case 'b': flagbody = 1; break;
193 case 'B': flagbody = 0; break;
5b62e993
MW
194 case 'c': flagrejectcommands = 1; break;
195 case 'C': flagrejectcommands = 0; break;
f8beb284
MW
196 case 'f': flagforward = 1; break;
197 case 'F': flagforward = 0; break;
198 case 'h': flagheaderreject = 1; break;
199 case 'H': flagheaderreject = 0; break;
200 case 'q': exitquiet = 99; break;
201 case 'Q': exitquiet = 100; break;
5b62e993
MW
202 case 's': flagneedsubject = 1; break;
203 case 'S': flagneedsubject = 0; break;
f8beb284
MW
204 case 't': flagtook = 0; break;
205 case 'T': flagtook = 1; break;
206 case 'v':
207 case 'V': strerr_die2x(0,
208 "ezmlm-reject: version ezmlm-0.53+",EZIDX_VERSION);
209
210 default: die_usage();
211 }
212 dir = argv[optind];
213 if (dir) {
214 if (chdir(dir) == -1)
215 strerr_die4x(111,FATAL,ERR_SWITCH,dir,": ");
216 flagparsemime = 1; /* only if dir do we have mimeremove/reject */
217 if (getconf_line(&line,"msgsize",0,FATAL,dir)) {
218 if (!stralloc_0(&line)) die_nomem();
219 len = scan_ulong(line.s,&maxmsgsize);
220 if (line.s[len] == ':')
221 scan_ulong(line.s+len+1,&minmsgsize);
5b62e993 222 }
f8beb284
MW
223 if (!flagtook || flagforward) {
224 getconf_line(&outlocal,"outlocal",1,FATAL,dir);
225 getconf_line(&outhost,"outhost",1,FATAL,dir);
226 }
227 if (flagforward) {
228 if (!stralloc_copys(&mydtline,"Delivered-To: command forwarder for "))
229 die_nomem();
230 if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem();
231 if (!stralloc_cats(&mydtline,"@")) die_nomem();
232 if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem();
233 if (!stralloc_cats(&mydtline,"\n")) die_nomem();
234 }
235 } else {
236 flagtook = 1; /* if no "dir" we can't get outlocal/outhost */
237 flagforward = 0; /* nor forward requests */
238 }
5b62e993 239
f8beb284
MW
240 if (flagparsemime) { /* set up MIME parsing */
241 getconf(&mimeremove,"mimeremove",0,FATAL,dir);
242 constmap_init(&mimeremovemap,mimeremove.s,mimeremove.len,0);
243 getconf(&mimereject,"mimereject",0,FATAL,dir);
244 constmap_init(&mimerejectmap,mimereject.s,mimereject.len,0);
245 }
246 if (flagheaderreject) {
247 if (!dir) die_usage();
248 getconf(&headerreject,"headerreject",1,FATAL,dir);
249 constmap_init(&headerrejectmap,headerreject.s,headerreject.len,0);
250 }
5b62e993 251 for (;;) {
f8beb284
MW
252 if (getln(&ssin,&line,&match,'\n') == -1)
253 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
5b62e993 254 if (!match) break;
f8beb284
MW
255 if (flagheaderreject)
256 if (constmap(&headerrejectmap,line.s,byte_chr(line.s,line.len,':')))
257 strerr_die2x(100,FATAL,ERR_MAILING_LIST);
258
5b62e993 259 if (line.len == 1) break;
f8beb284
MW
260 cp = line.s; len = line.len;
261 if ((*cp == ' ' || *cp == '\t')) {
262 switch(linetype) {
263 case 'T': if (!stralloc_catb(&to,cp,len-1)) die_nomem(); break;
264 case 'S': if (!stralloc_catb(&subject,cp,len-1)) die_nomem(); break;
265 case 'C': if (!stralloc_catb(&content,cp,len-1)) die_nomem(); break;
266 case 'P': if (!stralloc_catb(&precd,cp,len-1)) die_nomem(); break;
267 default: break;
268 }
269 } else {
270 if (!flagtook &&
271 (case_startb(cp,len,"to:") || case_startb(cp,len,"cc:"))) {
272 linetype = 'T'; /* cat so that To/Cc don't overwrite */
273 if (!stralloc_catb(&to,line.s + 3,line.len - 4)) die_nomem();
274 } else if ((flagneedsubject || flagrejectcommands) &&
275 case_startb(cp,len,"subject:")) {
276 if (!stralloc_copyb(&subject,cp+8,len-9)) die_nomem();
277 linetype = 'S';
278 } else if (case_startb(cp,len,"content-type:")) {
279 if (!stralloc_copyb(&content,cp+13,len-14)) die_nomem();
280 linetype = 'C';
281 } else if (case_startb(cp,len,"precedence:")) {
282 if (!stralloc_copyb(&precd,cp+11,len-12)) die_nomem();
283 linetype = 'P';
284 } else {
285 if (flagforward && line.len == mydtline.len) {
286 if (!byte_diff(line.s,line.len,mydtline.s))
287 strerr_die2x(100,FATAL,ERR_LOOPING);
288 }
289 linetype = ' ';
290 }
291 }
292 }
293 if (precd.len >= 4 &&
294 (!case_diffb(precd.s + precd.len - 4,4,"junk") ||
295 !case_diffb(precd.s + precd.len - 4,4,"bulk")))
296 strerr_die1x(99,ERR_JUNK); /* ignore precedence junk/bulk */
297 cp = subject.s;
298 len = subject.len;
299 while (len && (cp[len-1] == ' ' || cp[len-1] == '\t')) --len;
300 while (len && ((*cp == ' ') || (*cp == '\t'))) { ++cp; --len; }
301 flaghavesubject = 1;
302
303 if (flagbody)
304 if (len > 9 && case_starts(cp,"subscribe") ||
305 len > 11 && case_starts(cp,"unsubscribe"))
306 flaghavecommand = 1;
5b62e993 307
f8beb284
MW
308 switch(len) {
309 case 0: flaghavesubject = 0; break;
310 case 4: if (!case_diffb("help",4,cp)) flaghavecommand = 1; break;
311 case 6: /* Why can't they just leave an empty subject empty? */
312 if (!case_diffb("(null)",6,cp))
313 flaghavesubject = 0;
314 else
315 if (!case_diffb("(none)",6,cp))
316 flaghavesubject = 0;
317 else
318 if (!case_diffb("remove",6,cp))
319 flaghavecommand = 1;
320 break;
321 case 9: if (!case_diffb("subscribe",9,cp)) flaghavecommand = 1; break;
322 case 11: if (!case_diffb("unsubscribe",11,cp)) flaghavecommand = 1; break;
323 case 12: if (!case_diffb("(no subject)",12,cp)) flaghavesubject = 0; break;
324 default: break;
325 }
5b62e993 326
f8beb284
MW
327 if (!flagtook && !getto(&to))
328 strerr_die2x(exitquiet,FATAL,ERR_NO_ADDRESS);
5b62e993 329
f8beb284
MW
330 if (flagneedsubject && !flaghavesubject)
331 strerr_die2x(100,FATAL,ERR_NO_SUBJECT);
5b62e993 332
f8beb284
MW
333 if (flagrejectcommands && flaghavecommand)
334 if (flagforward) { /* flagforward => forward */
335 sender = env_get("SENDER");
336 if (!sender || !*sender) /* can't [won't] forward */
337 strerr_die2x(100,FATAL,ERR_SUBCOMMAND);
338 if (qmail_open(&qq,(stralloc *) 0) == -1) /* open queue */
339 strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
340 qmail_put(&qq,mydtline.s,mydtline.len);
341 if (seek_begin(0) == -1)
342 strerr_die2sys(111,FATAL,ERR_SEEK_INPUT);
343 if (substdio_copy(&ssqq,&ssin2) != 0)
344 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
345 if (!stralloc_copy(&to,&outlocal)) die_nomem();
346 if (!stralloc_cats(&to,"-request@")) die_nomem();
347 if (!stralloc_cat(&to,&outhost)) die_nomem();
348 if (!stralloc_0(&to)) die_nomem();
349 qmail_from(&qq,sender);
350 qmail_to(&qq,to.s);
351 if (*(err = qmail_close(&qq)) == '\0') {
352 strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
353 strerr_die2x(99,"ezmlm-request: info: forward qp ",strnum);
354 } else
355 strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
356 } else
357 strerr_die2x(100,FATAL,ERR_SUBCOMMAND);
5b62e993 358
f8beb284
MW
359 if (content.len) { /* MIME header */
360 cp = content.s;
361 len = content.len;
362 while (len && *cp == ' ' || *cp == '\t') { ++cp; --len; }
363 cpstart = cp;
364 if (*cp == '"') { /* might be commented */
365 ++cp; cpstart = cp;
366 while (len && *cp != '"') { ++cp; --len; }
367 } else {
368 while (len && *cp != ' ' && *cp != '\t' && *cp != ';') {
369 ++cp; --len;
5b62e993
MW
370 }
371 }
5b62e993 372
f8beb284
MW
373 if (flagparsemime)
374 if (constmap(&mimeremovemap,cpstart,cp-cpstart) ||
375 constmap(&mimerejectmap,cpstart,cp-cpstart)) {
376 *(cp) = (char) 0;
377 strerr_die5x(100,FATAL,ERR_BAD_TYPE,cpstart,"'",ERR_SIZE_CODE);
378 }
5b62e993 379
f8beb284
MW
380 cpafter = content.s+content.len;
381 while((cp += byte_chr(cp,cpafter-cp,';')) != cpafter) {
382 ++cp;
383 while (cp < cpafter && (*cp == ' ') || (*cp == '\t')) ++cp;
384 if (case_startb(cp,cpafter - cp,"boundary=")) {
385 cp += 9; /* after boundary= */
386 if (cp < cpafter && *cp == '"') {
387 ++cp;
388 cpstart = cp;
389 while (cp < cpafter && *cp != '"') ++cp;
390 if (cp == cpafter)
391 strerr_die1x(100,ERR_MIME_QUOTE);
392 } else {
393 cpstart = cp;
394 while (cp < cpafter &&
395 *cp != ';' && *cp != ' ' && *cp != '\t') ++cp;
396 }
397 if (!stralloc_copys(&boundary,"--")) die_nomem();
398 if (!stralloc_catb(&boundary,cpstart,cp-cpstart))
399 die_nomem();
400 break;
401 }
402 } /* got boundary, now parse for parts */
403 }
5b62e993 404
f8beb284
MW
405 for (;;) {
406 if (getln(&ssin,&line,&match,'\n') == -1)
407 strerr_die2sys(111,FATAL,ERR_READ_INPUT);
408 if (!match) break;
409 if (line.len == 1) {
410 flagcheck = 0;
411 continue;
412 /* Doesn't do continuation lines. _very_ unusual, and worst */
413 /* case one slips through that shouldn't have */
414 } else if (flagcheck && case_startb(line.s,line.len,"content-type:")) {
415 cp = line.s + 13;
416 len = line.len - 14; /* zap '\n' */
417 while (*cp == ' ' || *cp == '\t') { ++cp; --len; }
418 cpstart = cp;
419 if (*cp == '"') { /* quoted */
420 ++cp; cpstart = cp;
421 while (len && *cp != '"') { ++cp; --len; }
422 } else { /* not quoted */
423 while (len && *cp != ' ' && *cp != '\t' && *cp != ';') {
424 ++cp; --len;
425 }
426 }
427 if (flagparsemime && constmap(&mimerejectmap,cpstart,cp-cpstart)) {
428 *cp = '\0';
429 strerr_die4x(100,FATAL,ERR_BAD_PART,cpstart,ERR_SIZE_CODE);
430 }
431 } else if (boundary.len && *line.s == '-' && line.len > boundary.len &&
432 !str_diffn(line.s,boundary.s,boundary.len)) {
433 flagcheck = 1;
434 } else {
435 if (!msgsize && flagbody)
436 if (case_startb(line.s,line.len,"subscribe") ||
437 case_startb(line.s,line.len,"unsubscribe"))
438 strerr_die2x(100,FATAL,ERR_BODYCOMMAND);
439 if (!flagcheck) {
440 msgsize += line.len;
441 if (maxmsgsize && msgsize > maxmsgsize) {
442 strnum[fmt_ulong(strnum,maxmsgsize)] = 0;
443 strerr_die5x(100,FATAL,ERR_MAX_SIZE,strnum," bytes",ERR_SIZE_CODE);
444 }
445 }
446 }
447 }
448 if (msgsize < minmsgsize) {
449 strnum[fmt_ulong(strnum,minmsgsize)] = 0;
450 strerr_die5x(100,FATAL,ERR_MIN_SIZE,strnum," bytes",ERR_SIZE_CODE);
451 }
5b62e993
MW
452 _exit(0);
453}