Commit | Line | Data |
---|---|---|
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 |
19 | int flagrejectcommands = 1; /* reject if subject is simple command */ |
20 | int flagneedsubject = 1; /* reject if subject is missing */ | |
21 | int flagtook = 0; /* reject unless listaddress in To: or Cc: */ | |
22 | int exitquiet = 100; /* reject with error (100) rather than exit */ | |
23 | /* quietly (99) if listaddress missing */ | |
24 | int flagheaderreject = 0; /* don't reject messages with headers from */ | |
25 | /* other mailing lists. */ | |
26 | int flagbody = 0; /* =1 => reject is subject or body starts with*/ | |
27 | /* "subscribe" or "unsubscribe" */ | |
28 | int flagforward = 0; /* =1 => forward commands to list-request */ | |
29 | int flagparsemime = 0; | |
5b62e993 MW |
30 | int flaghavesubject = 0; |
31 | int flaghavecommand = 0; | |
f8beb284 MW |
32 | int flagcheck = 0; /* set after boundary is found in body, */ |
33 | /* until blank line */ | |
5b62e993 | 34 | |
f8beb284 MW |
35 | stralloc mimeremove = {0}; |
36 | stralloc mimereject = {0}; | |
37 | stralloc headerreject = {0}; | |
38 | ||
39 | struct constmap mimeremovemap; | |
40 | struct constmap mimerejectmap; | |
41 | struct constmap headerrejectmap; | |
42 | ||
43 | char strnum[FMT_ULONG]; | |
5b62e993 | 44 | char buf0[256]; |
f8beb284 MW |
45 | substdio ssin = SUBSTDIO_FDBUF(read,0,buf0,(int) sizeof(buf0)); |
46 | substdio ssin2 = SUBSTDIO_FDBUF(read,0,buf0,(int) sizeof(buf0)); | |
47 | ||
48 | struct qmail qq; | |
49 | int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len; | |
50 | { | |
51 | qmail_put(&qq,buf,len); | |
52 | return len; | |
53 | } | |
54 | ||
55 | char qqbuf[1]; | |
56 | substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf)); | |
57 | ||
5b62e993 | 58 | stralloc line = {0}; |
f8beb284 MW |
59 | stralloc to = {0}; |
60 | stralloc outhost = {0}; | |
61 | stralloc outlocal = {0}; | |
62 | stralloc content = {0}; | |
63 | stralloc subject = {0}; | |
64 | stralloc boundary = {0}; | |
65 | stralloc precd = {0}; | |
66 | stralloc mydtline = {0}; | |
67 | ||
68 | void die_nomem() | |
69 | { | |
70 | strerr_die2x(100,FATAL,ERR_NOMEM); | |
71 | } | |
72 | ||
73 | void die_usage() | |
74 | { | |
75 | strerr_die2x(100,FATAL,"usage: ezmlm-reject [-bBcCfFhHqQsStT] [dir]"); | |
76 | } | |
77 | ||
78 | unsigned int findlocal(sa,n) | |
79 | /* n is index of '@' within sa. Returns index to last postition */ | |
80 | /* of local, n otherwise. */ | |
81 | stralloc *sa; /* line */ | |
82 | unsigned 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 | ||
114 | unsigned 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. */ | |
117 | stralloc *sa; /* line */ | |
118 | unsigned 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 | ||
150 | int 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 | |
174 | void main(argc,argv) | |
175 | int argc; | |
176 | char **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 | } |