17 #define FATAL "ezmlm-reject: fatal: "
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;
30 int flaghavesubject
= 0;
31 int flaghavecommand
= 0;
32 int flagcheck
= 0; /* set after boundary is found in body, */
33 /* until blank line */
35 stralloc mimeremove
= {0};
36 stralloc mimereject
= {0};
37 stralloc headerreject
= {0};
39 struct constmap mimeremovemap
;
40 struct constmap mimerejectmap
;
41 struct constmap headerrejectmap
;
43 char strnum
[FMT_ULONG
];
45 substdio ssin
= SUBSTDIO_FDBUF(read
,0,buf0
,(int) sizeof(buf0
));
46 substdio ssin2
= SUBSTDIO_FDBUF(read
,0,buf0
,(int) sizeof(buf0
));
49 int qqwrite(fd
,buf
,len
) int fd
; char *buf
; unsigned int len
;
51 qmail_put(&qq
,buf
,len
);
56 substdio ssqq
= SUBSTDIO_FDBUF(qqwrite
,-1,qqbuf
,(int) sizeof(qqbuf
));
60 stralloc outhost
= {0};
61 stralloc outlocal
= {0};
62 stralloc content
= {0};
63 stralloc subject
= {0};
64 stralloc boundary
= {0};
66 stralloc mydtline
= {0};
70 strerr_die2x(100,FATAL
,ERR_NOMEM
);
75 strerr_die2x(100,FATAL
,"usage: ezmlm-reject [-bBcCfFhHqQsStT] [dir]");
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 */
86 register int level
= 0;
90 if (s
<= first
) return n
;
91 while (--s
>= first
) {
93 case ' ': case '\t': case '\n': break;
95 if (--s
<= first
) return n
;
96 if (*s
== '\\') break;
98 while (level
&& --s
> first
) {
99 if (*s
== ')') if (*(s
-1) != '\\') ++level
;
100 if (*s
== '(') if (*(s
-1) != '\\') --level
;
105 if (s
< first
) return n
;
106 return (unsigned int) (s
- first
);
108 return (unsigned int) (s
- first
);
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 */
122 register int level
= 0;
124 last
= sa
->s
+ sa
->len
- 1;
126 if (s
>= last
) return n
;
127 while (++s
<= last
) {
129 case ' ': case '\t': case '\n': break;
132 while (level
&& (++s
< last
)) {
133 if (*s
== ')') --level
; if (!level
) break;
134 if (*s
== '(') ++level
;
140 if (*s
== '"') break;
145 return (unsigned int) (s
- sa
->s
);
151 /* find list address in line. If found, return 1, else return 0. */
154 unsigned int pos
= 0;
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 */
178 unsigned long maxmsgsize
= 0L;
179 unsigned long minmsgsize
= 0L;
180 unsigned long msgsize
= 0L;
183 char *cp
, *cpstart
, *cpafter
;
190 while ((opt
= getopt(argc
,argv
,"bBcCfFhHqQsStT")) != opteof
)
192 case 'b': flagbody
= 1; break;
193 case 'B': flagbody
= 0; break;
194 case 'c': flagrejectcommands
= 1; break;
195 case 'C': flagrejectcommands
= 0; break;
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;
202 case 's': flagneedsubject
= 1; break;
203 case 'S': flagneedsubject
= 0; break;
204 case 't': flagtook
= 0; break;
205 case 'T': flagtook
= 1; break;
207 case 'V': strerr_die2x(0,
208 "ezmlm-reject: version ezmlm-0.53+",EZIDX_VERSION
);
210 default: die_usage();
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
);
223 if (!flagtook
|| flagforward
) {
224 getconf_line(&outlocal
,"outlocal",1,FATAL
,dir
);
225 getconf_line(&outhost
,"outhost",1,FATAL
,dir
);
228 if (!stralloc_copys(&mydtline
,"Delivered-To: command forwarder for "))
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();
236 flagtook
= 1; /* if no "dir" we can't get outlocal/outhost */
237 flagforward
= 0; /* nor forward requests */
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);
246 if (flagheaderreject
) {
247 if (!dir
) die_usage();
248 getconf(&headerreject
,"headerreject",1,FATAL
,dir
);
249 constmap_init(&headerrejectmap
,headerreject
.s
,headerreject
.len
,0);
252 if (getln(&ssin
,&line
,&match
,'\n') == -1)
253 strerr_die2sys(111,FATAL
,ERR_READ_INPUT
);
255 if (flagheaderreject
)
256 if (constmap(&headerrejectmap
,line
.s
,byte_chr(line
.s
,line
.len
,':')))
257 strerr_die2x(100,FATAL
,ERR_MAILING_LIST
);
259 if (line
.len
== 1) break;
260 cp
= line
.s
; len
= line
.len
;
261 if ((*cp
== ' ' || *cp
== '\t')) {
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;
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();
278 } else if (case_startb(cp
,len
,"content-type:")) {
279 if (!stralloc_copyb(&content
,cp
+13,len
-14)) die_nomem();
281 } else if (case_startb(cp
,len
,"precedence:")) {
282 if (!stralloc_copyb(&precd
,cp
+11,len
-12)) die_nomem();
285 if (flagforward
&& line
.len
== mydtline
.len
) {
286 if (!byte_diff(line
.s
,line
.len
,mydtline
.s
))
287 strerr_die2x(100,FATAL
,ERR_LOOPING
);
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 */
299 while (len
&& (cp
[len
-1] == ' ' || cp
[len
-1] == '\t')) --len
;
300 while (len
&& ((*cp
== ' ') || (*cp
== '\t'))) { ++cp
; --len
; }
304 if (len
> 9 && case_starts(cp
,"subscribe") ||
305 len
> 11 && case_starts(cp
,"unsubscribe"))
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
))
315 if (!case_diffb("(none)",6,cp
))
318 if (!case_diffb("remove",6,cp
))
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;
327 if (!flagtook
&& !getto(&to
))
328 strerr_die2x(exitquiet
,FATAL
,ERR_NO_ADDRESS
);
330 if (flagneedsubject
&& !flaghavesubject
)
331 strerr_die2x(100,FATAL
,ERR_NO_SUBJECT
);
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
);
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
);
355 strerr_die3x(111,FATAL
,ERR_TMP_QMAIL_QUEUE
,err
+ 1);
357 strerr_die2x(100,FATAL
,ERR_SUBCOMMAND
);
359 if (content
.len
) { /* MIME header */
362 while (len
&& *cp
== ' ' || *cp
== '\t') { ++cp
; --len
; }
364 if (*cp
== '"') { /* might be commented */
366 while (len
&& *cp
!= '"') { ++cp
; --len
; }
368 while (len
&& *cp
!= ' ' && *cp
!= '\t' && *cp
!= ';') {
374 if (constmap(&mimeremovemap
,cpstart
,cp
-cpstart
) ||
375 constmap(&mimerejectmap
,cpstart
,cp
-cpstart
)) {
377 strerr_die5x(100,FATAL
,ERR_BAD_TYPE
,cpstart
,"'",ERR_SIZE_CODE
);
380 cpafter
= content
.s
+content
.len
;
381 while((cp
+= byte_chr(cp
,cpafter
-cp
,';')) != cpafter
) {
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
== '"') {
389 while (cp
< cpafter
&& *cp
!= '"') ++cp
;
391 strerr_die1x(100,ERR_MIME_QUOTE
);
394 while (cp
< cpafter
&&
395 *cp
!= ';' && *cp
!= ' ' && *cp
!= '\t') ++cp
;
397 if (!stralloc_copys(&boundary
,"--")) die_nomem();
398 if (!stralloc_catb(&boundary
,cpstart
,cp
-cpstart
))
402 } /* got boundary, now parse for parts */
406 if (getln(&ssin
,&line
,&match
,'\n') == -1)
407 strerr_die2sys(111,FATAL
,ERR_READ_INPUT
);
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:")) {
416 len
= line
.len
- 14; /* zap '\n' */
417 while (*cp
== ' ' || *cp
== '\t') { ++cp
; --len
; }
419 if (*cp
== '"') { /* quoted */
421 while (len
&& *cp
!= '"') { ++cp
; --len
; }
422 } else { /* not quoted */
423 while (len
&& *cp
!= ' ' && *cp
!= '\t' && *cp
!= ';') {
427 if (flagparsemime
&& constmap(&mimerejectmap
,cpstart
,cp
-cpstart
)) {
429 strerr_die4x(100,FATAL
,ERR_BAD_PART
,cpstart
,ERR_SIZE_CODE
);
431 } else if (boundary
.len
&& *line
.s
== '-' && line
.len
> boundary
.len
&&
432 !str_diffn(line
.s
,boundary
.s
,boundary
.len
)) {
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
);
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
);
448 if (msgsize
< minmsgsize
) {
449 strnum
[fmt_ulong(strnum
,minmsgsize
)] = 0;
450 strerr_die5x(100,FATAL
,ERR_MIN_SIZE
,strnum
," bytes",ERR_SIZE_CODE
);