chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-reject.c
1 #include "strerr.h"
2 #include "substdio.h"
3 #include "readwrite.h"
4 #include "stralloc.h"
5 #include "getln.h"
6 #include "sgetopt.h"
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"
16
17 #define FATAL "ezmlm-reject: fatal: "
18
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 */
34
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];
44 char buf0[256];
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
58 stralloc line = {0};
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 }
173
174 void main(argc,argv)
175 int argc;
176 char **argv;
177 {
178   unsigned long maxmsgsize = 0L;
179   unsigned long minmsgsize = 0L;
180   unsigned long msgsize = 0L;
181   int opt;
182   char linetype = ' ';
183   char *cp, *cpstart, *cpafter;
184   char *dir;
185   char *err;
186   char *sender;
187   unsigned int len;
188   int match;
189
190   while ((opt = getopt(argc,argv,"bBcCfFhHqQsStT")) != opteof)
191     switch(opt) {
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;
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);
222     }
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   }
239
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   }
251   for (;;) {
252     if (getln(&ssin,&line,&match,'\n') == -1)
253       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
254     if (!match) break;
255     if (flagheaderreject)
256       if (constmap(&headerrejectmap,line.s,byte_chr(line.s,line.len,':')))
257         strerr_die2x(100,FATAL,ERR_MAILING_LIST);
258
259     if (line.len == 1) break;
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;
307
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   }
326
327   if (!flagtook && !getto(&to))
328     strerr_die2x(exitquiet,FATAL,ERR_NO_ADDRESS);
329
330   if (flagneedsubject && !flaghavesubject)
331     strerr_die2x(100,FATAL,ERR_NO_SUBJECT);
332
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);
358
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;
370       }
371     }
372
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     }
379
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   }
404
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   }
452   _exit(0);
453 }