X-Git-Url: https://git.distorted.org.uk/~mdw/ezmlm/blobdiff_plain/5b62e993b0af39700031c2875d7f6654e6a02850..f8beb284087c279acfb30506f5bb32baa4949b44:/ezmlm-reject.c diff --git a/ezmlm-reject.c b/ezmlm-reject.c index ffeea99..55195e0 100644 --- a/ezmlm-reject.c +++ b/ezmlm-reject.c @@ -4,79 +4,450 @@ #include "stralloc.h" #include "getln.h" #include "sgetopt.h" +#include "getconf.h" +#include "constmap.h" +#include "fmt.h" +#include "qmail.h" +#include "seek.h" +#include "scan.h" +#include "env.h" +#include "errtxt.h" +#include "idx.h" -int flagrejectcommands = 1; -int flagneedsubject = 1; +#define FATAL "ezmlm-reject: fatal: " +int flagrejectcommands = 1; /* reject if subject is simple command */ +int flagneedsubject = 1; /* reject if subject is missing */ +int flagtook = 0; /* reject unless listaddress in To: or Cc: */ +int exitquiet = 100; /* reject with error (100) rather than exit */ + /* quietly (99) if listaddress missing */ +int flagheaderreject = 0; /* don't reject messages with headers from */ + /* other mailing lists. */ +int flagbody = 0; /* =1 => reject is subject or body starts with*/ + /* "subscribe" or "unsubscribe" */ +int flagforward = 0; /* =1 => forward commands to list-request */ +int flagparsemime = 0; int flaghavesubject = 0; int flaghavecommand = 0; +int flagcheck = 0; /* set after boundary is found in body, */ + /* until blank line */ +stralloc mimeremove = {0}; +stralloc mimereject = {0}; +stralloc headerreject = {0}; + +struct constmap mimeremovemap; +struct constmap mimerejectmap; +struct constmap headerrejectmap; + +char strnum[FMT_ULONG]; char buf0[256]; -substdio ss0 = SUBSTDIO_FDBUF(read,0,buf0,sizeof(buf0)); +substdio ssin = SUBSTDIO_FDBUF(read,0,buf0,(int) sizeof(buf0)); +substdio ssin2 = SUBSTDIO_FDBUF(read,0,buf0,(int) sizeof(buf0)); + +struct qmail qq; +int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len; +{ + qmail_put(&qq,buf,len); + return len; +} + +char qqbuf[1]; +substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,(int) sizeof(qqbuf)); + stralloc line = {0}; +stralloc to = {0}; +stralloc outhost = {0}; +stralloc outlocal = {0}; +stralloc content = {0}; +stralloc subject = {0}; +stralloc boundary = {0}; +stralloc precd = {0}; +stralloc mydtline = {0}; + +void die_nomem() +{ + strerr_die2x(100,FATAL,ERR_NOMEM); +} + +void die_usage() +{ + strerr_die2x(100,FATAL,"usage: ezmlm-reject [-bBcCfFhHqQsStT] [dir]"); +} + +unsigned int findlocal(sa,n) + /* n is index of '@' within sa. Returns index to last postition */ + /* of local, n otherwise. */ +stralloc *sa; /* line */ +unsigned int n; +{ + char *first; + register char *s; + register int level = 0; + + first = sa->s; + s = sa->s + n; + if (s <= first) return n; + while (--s >= first) { + switch (*s) { + case ' ': case '\t': case '\n': break; + case ')': + if (--s <= first) return n; + if (*s == '\\') break; + ++level; ++s; + while (level && --s > first) { + if (*s == ')') if (*(s-1) != '\\') ++level; + if (*s == '(') if (*(s-1) != '\\') --level; + } + break; + case '"': + --s; + if (s < first) return n; + return (unsigned int) (s - first); + default: + return (unsigned int) (s - first); + } +#include "env.h" + } +} + +unsigned int findhost(sa,n) + /* s in index to a '@' within sa. Returns index to first pos of */ + /* host part if there is one, n otherwise. */ +stralloc *sa; /* line */ +unsigned int n; +{ + char *last; + register char *s; + register int level = 0; + + last = sa->s + sa->len - 1; + s = sa->s + n; + if (s >= last) return n; + while (++s <= last) { + switch (*s) { + case ' ': case '\t': case '\n': break; + case '(': + ++level; + while (level && (++s < last)) { + if (*s == ')') --level; if (!level) break; + if (*s == '(') ++level; + if (*s == '\\') ++s; + } + break; + case '"': + while (++s < last) { + if (*s == '"') break; + if (*s == '\\') ++s; + } + break; + default: + return (unsigned int) (s - sa->s); + } + } +} + +int getto(sa) + /* find list address in line. If found, return 1, else return 0. */ + stralloc *sa; +{ + unsigned int pos = 0; + unsigned int pos1; + + if (!sa->len) return 0; /* no To: or Cc: line */ + while ((pos += 1 + byte_chr(sa->s+pos+1,sa->len-pos-1,'@')) != sa->len) { + pos1 = findhost(sa,pos); + if (pos1 == pos) break; + if (pos1 + outhost.len <= sa->len) + if (!case_diffb(sa->s+pos1,outhost.len,outhost.s)) { /* got host */ + pos1 = findlocal(sa,pos); + if (pos1 == pos) break; + ++pos1; /* avoids 1 x 2 below */ + if (pos1 >= outlocal.len) + if (!case_diffb(sa->s+pos1-outlocal.len,outlocal.len,outlocal.s)) + return 1; /* got local as well */ + } + } + return 0; +} void main(argc,argv) int argc; char **argv; { + unsigned long maxmsgsize = 0L; + unsigned long minmsgsize = 0L; + unsigned long msgsize = 0L; int opt; - char *x; - int len; + char linetype = ' '; + char *cp, *cpstart, *cpafter; + char *dir; + char *err; + char *sender; + unsigned int len; int match; - while ((opt = getopt(argc,argv,"cCsS")) != opteof) + while ((opt = getopt(argc,argv,"bBcCfFhHqQsStT")) != opteof) switch(opt) { + case 'b': flagbody = 1; break; + case 'B': flagbody = 0; break; case 'c': flagrejectcommands = 1; break; case 'C': flagrejectcommands = 0; break; + case 'f': flagforward = 1; break; + case 'F': flagforward = 0; break; + case 'h': flagheaderreject = 1; break; + case 'H': flagheaderreject = 0; break; + case 'q': exitquiet = 99; break; + case 'Q': exitquiet = 100; break; case 's': flagneedsubject = 1; break; case 'S': flagneedsubject = 0; break; - default: - strerr_die1x(100,"ezmlm-reject: usage: ezmlm-reject [ -cCsS ]"); + case 't': flagtook = 0; break; + case 'T': flagtook = 1; break; + case 'v': + case 'V': strerr_die2x(0, + "ezmlm-reject: version ezmlm-0.53+",EZIDX_VERSION); + + default: die_usage(); + } + dir = argv[optind]; + if (dir) { + if (chdir(dir) == -1) + strerr_die4x(111,FATAL,ERR_SWITCH,dir,": "); + flagparsemime = 1; /* only if dir do we have mimeremove/reject */ + if (getconf_line(&line,"msgsize",0,FATAL,dir)) { + if (!stralloc_0(&line)) die_nomem(); + len = scan_ulong(line.s,&maxmsgsize); + if (line.s[len] == ':') + scan_ulong(line.s+len+1,&minmsgsize); } + if (!flagtook || flagforward) { + getconf_line(&outlocal,"outlocal",1,FATAL,dir); + getconf_line(&outhost,"outhost",1,FATAL,dir); + } + if (flagforward) { + if (!stralloc_copys(&mydtline,"Delivered-To: command forwarder for ")) + die_nomem(); + if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem(); + if (!stralloc_cats(&mydtline,"@")) die_nomem(); + if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem(); + if (!stralloc_cats(&mydtline,"\n")) die_nomem(); + } + } else { + flagtook = 1; /* if no "dir" we can't get outlocal/outhost */ + flagforward = 0; /* nor forward requests */ + } + if (flagparsemime) { /* set up MIME parsing */ + getconf(&mimeremove,"mimeremove",0,FATAL,dir); + constmap_init(&mimeremovemap,mimeremove.s,mimeremove.len,0); + getconf(&mimereject,"mimereject",0,FATAL,dir); + constmap_init(&mimerejectmap,mimereject.s,mimereject.len,0); + } + if (flagheaderreject) { + if (!dir) die_usage(); + getconf(&headerreject,"headerreject",1,FATAL,dir); + constmap_init(&headerrejectmap,headerreject.s,headerreject.len,0); + } for (;;) { - if (getln(&ss0,&line,&match,'\n') == -1) - strerr_die2sys(111,"ezmlm-reject: fatal: ","unable to read input: "); + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die2sys(111,FATAL,ERR_READ_INPUT); if (!match) break; + if (flagheaderreject) + if (constmap(&headerrejectmap,line.s,byte_chr(line.s,line.len,':'))) + strerr_die2x(100,FATAL,ERR_MAILING_LIST); + if (line.len == 1) break; + cp = line.s; len = line.len; + if ((*cp == ' ' || *cp == '\t')) { + switch(linetype) { + case 'T': if (!stralloc_catb(&to,cp,len-1)) die_nomem(); break; + case 'S': if (!stralloc_catb(&subject,cp,len-1)) die_nomem(); break; + case 'C': if (!stralloc_catb(&content,cp,len-1)) die_nomem(); break; + case 'P': if (!stralloc_catb(&precd,cp,len-1)) die_nomem(); break; + default: break; + } + } else { + if (!flagtook && + (case_startb(cp,len,"to:") || case_startb(cp,len,"cc:"))) { + linetype = 'T'; /* cat so that To/Cc don't overwrite */ + if (!stralloc_catb(&to,line.s + 3,line.len - 4)) die_nomem(); + } else if ((flagneedsubject || flagrejectcommands) && + case_startb(cp,len,"subject:")) { + if (!stralloc_copyb(&subject,cp+8,len-9)) die_nomem(); + linetype = 'S'; + } else if (case_startb(cp,len,"content-type:")) { + if (!stralloc_copyb(&content,cp+13,len-14)) die_nomem(); + linetype = 'C'; + } else if (case_startb(cp,len,"precedence:")) { + if (!stralloc_copyb(&precd,cp+11,len-12)) die_nomem(); + linetype = 'P'; + } else { + if (flagforward && line.len == mydtline.len) { + if (!byte_diff(line.s,line.len,mydtline.s)) + strerr_die2x(100,FATAL,ERR_LOOPING); + } + linetype = ' '; + } + } + } + if (precd.len >= 4 && + (!case_diffb(precd.s + precd.len - 4,4,"junk") || + !case_diffb(precd.s + precd.len - 4,4,"bulk"))) + strerr_die1x(99,ERR_JUNK); /* ignore precedence junk/bulk */ + cp = subject.s; + len = subject.len; + while (len && (cp[len-1] == ' ' || cp[len-1] == '\t')) --len; + while (len && ((*cp == ' ') || (*cp == '\t'))) { ++cp; --len; } + flaghavesubject = 1; + + if (flagbody) + if (len > 9 && case_starts(cp,"subscribe") || + len > 11 && case_starts(cp,"unsubscribe")) + flaghavecommand = 1; - x = line.s; len = line.len - 1; - while (len && ((x[len - 1] == ' ') || (x[len - 1] == '\t'))) --len; + switch(len) { + case 0: flaghavesubject = 0; break; + case 4: if (!case_diffb("help",4,cp)) flaghavecommand = 1; break; + case 6: /* Why can't they just leave an empty subject empty? */ + if (!case_diffb("(null)",6,cp)) + flaghavesubject = 0; + else + if (!case_diffb("(none)",6,cp)) + flaghavesubject = 0; + else + if (!case_diffb("remove",6,cp)) + flaghavecommand = 1; + break; + case 9: if (!case_diffb("subscribe",9,cp)) flaghavecommand = 1; break; + case 11: if (!case_diffb("unsubscribe",11,cp)) flaghavecommand = 1; break; + case 12: if (!case_diffb("(no subject)",12,cp)) flaghavesubject = 0; break; + default: break; + } - if (case_startb(x,len,"subject:")) { - x += 8; len -= 8; - while (len && ((*x == ' ') || (*x == '\t'))) { ++x; --len; } - if (len) { - flaghavesubject = 1; + if (!flagtook && !getto(&to)) + strerr_die2x(exitquiet,FATAL,ERR_NO_ADDRESS); - if (len == 4) - if (!case_diffb("help",4,x)) - flaghavecommand = 1; + if (flagneedsubject && !flaghavesubject) + strerr_die2x(100,FATAL,ERR_NO_SUBJECT); - if (len == 9) - if (!case_diffb("subscribe",9,x)) - flaghavecommand = 1; + if (flagrejectcommands && flaghavecommand) + if (flagforward) { /* flagforward => forward */ + sender = env_get("SENDER"); + if (!sender || !*sender) /* can't [won't] forward */ + strerr_die2x(100,FATAL,ERR_SUBCOMMAND); + if (qmail_open(&qq,(stralloc *) 0) == -1) /* open queue */ + strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE); + qmail_put(&qq,mydtline.s,mydtline.len); + if (seek_begin(0) == -1) + strerr_die2sys(111,FATAL,ERR_SEEK_INPUT); + if (substdio_copy(&ssqq,&ssin2) != 0) + strerr_die2sys(111,FATAL,ERR_READ_INPUT); + if (!stralloc_copy(&to,&outlocal)) die_nomem(); + if (!stralloc_cats(&to,"-request@")) die_nomem(); + if (!stralloc_cat(&to,&outhost)) die_nomem(); + if (!stralloc_0(&to)) die_nomem(); + qmail_from(&qq,sender); + qmail_to(&qq,to.s); + if (*(err = qmail_close(&qq)) == '\0') { + strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0; + strerr_die2x(99,"ezmlm-request: info: forward qp ",strnum); + } else + strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1); + } else + strerr_die2x(100,FATAL,ERR_SUBCOMMAND); - if (len == 11) - if (!case_diffb("unsubscribe",11,x)) - flaghavecommand = 1; + if (content.len) { /* MIME header */ + cp = content.s; + len = content.len; + while (len && *cp == ' ' || *cp == '\t') { ++cp; --len; } + cpstart = cp; + if (*cp == '"') { /* might be commented */ + ++cp; cpstart = cp; + while (len && *cp != '"') { ++cp; --len; } + } else { + while (len && *cp != ' ' && *cp != '\t' && *cp != ';') { + ++cp; --len; } } - } - if (flagneedsubject && !flaghavesubject) - strerr_die1x(100,"\ -ezmlm-reject: fatal: I need a nonempty Subject line in every message.\n\ -If you are trying to subscribe or unsubscribe, WRONG ADDRESS!\n\ -Do not send administrative requests to the mailing list.\n\ -Send an empty message to ...-help@... for automated assistance."); + if (flagparsemime) + if (constmap(&mimeremovemap,cpstart,cp-cpstart) || + constmap(&mimerejectmap,cpstart,cp-cpstart)) { + *(cp) = (char) 0; + strerr_die5x(100,FATAL,ERR_BAD_TYPE,cpstart,"'",ERR_SIZE_CODE); + } - if (flagrejectcommands && flaghavecommand) - strerr_die1x(100,"\ -ezmlm-reject: fatal: Your Subject line looks like a command word.\n\ -If you are trying to subscribe or unsubscribe, WRONG ADDRESS!\n\ -Do not send administrative requests to the mailing list.\n\ -Send an empty message to ...-help@... for automated assistance."); + cpafter = content.s+content.len; + while((cp += byte_chr(cp,cpafter-cp,';')) != cpafter) { + ++cp; + while (cp < cpafter && (*cp == ' ') || (*cp == '\t')) ++cp; + if (case_startb(cp,cpafter - cp,"boundary=")) { + cp += 9; /* after boundary= */ + if (cp < cpafter && *cp == '"') { + ++cp; + cpstart = cp; + while (cp < cpafter && *cp != '"') ++cp; + if (cp == cpafter) + strerr_die1x(100,ERR_MIME_QUOTE); + } else { + cpstart = cp; + while (cp < cpafter && + *cp != ';' && *cp != ' ' && *cp != '\t') ++cp; + } + if (!stralloc_copys(&boundary,"--")) die_nomem(); + if (!stralloc_catb(&boundary,cpstart,cp-cpstart)) + die_nomem(); + break; + } + } /* got boundary, now parse for parts */ + } + for (;;) { + if (getln(&ssin,&line,&match,'\n') == -1) + strerr_die2sys(111,FATAL,ERR_READ_INPUT); + if (!match) break; + if (line.len == 1) { + flagcheck = 0; + continue; + /* Doesn't do continuation lines. _very_ unusual, and worst */ + /* case one slips through that shouldn't have */ + } else if (flagcheck && case_startb(line.s,line.len,"content-type:")) { + cp = line.s + 13; + len = line.len - 14; /* zap '\n' */ + while (*cp == ' ' || *cp == '\t') { ++cp; --len; } + cpstart = cp; + if (*cp == '"') { /* quoted */ + ++cp; cpstart = cp; + while (len && *cp != '"') { ++cp; --len; } + } else { /* not quoted */ + while (len && *cp != ' ' && *cp != '\t' && *cp != ';') { + ++cp; --len; + } + } + if (flagparsemime && constmap(&mimerejectmap,cpstart,cp-cpstart)) { + *cp = '\0'; + strerr_die4x(100,FATAL,ERR_BAD_PART,cpstart,ERR_SIZE_CODE); + } + } else if (boundary.len && *line.s == '-' && line.len > boundary.len && + !str_diffn(line.s,boundary.s,boundary.len)) { + flagcheck = 1; + } else { + if (!msgsize && flagbody) + if (case_startb(line.s,line.len,"subscribe") || + case_startb(line.s,line.len,"unsubscribe")) + strerr_die2x(100,FATAL,ERR_BODYCOMMAND); + if (!flagcheck) { + msgsize += line.len; + if (maxmsgsize && msgsize > maxmsgsize) { + strnum[fmt_ulong(strnum,maxmsgsize)] = 0; + strerr_die5x(100,FATAL,ERR_MAX_SIZE,strnum," bytes",ERR_SIZE_CODE); + } + } + } + } + if (msgsize < minmsgsize) { + strnum[fmt_ulong(strnum,minmsgsize)] = 0; + strerr_die5x(100,FATAL,ERR_MIN_SIZE,strnum," bytes",ERR_SIZE_CODE); + } _exit(0); }