| 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 | } |