1 #include "common.h" 2 #include "spam.h" 3 4 int cflag; 5 int debug; 6 int hflag; 7 int nflag; 8 int sflag; 9 int tflag; 10 int vflag; 11 Biobuf bin, bout, *cout; 12 13 /* file names */ 14 char patfile[128]; 15 char linefile[128]; 16 char holdqueue[128]; 17 char copydir[128]; 18 19 char header[Hdrsize+2]; 20 char cmd[1024]; 21 char **qname; 22 char **qdir; 23 char *sender; 24 String *recips; 25 26 char* canon(Biobuf*, char*, char*, int*); 27 int matcher(char*, Pattern*, char*, Resub*); 28 int matchaction(int, char*, Resub*); 29 Biobuf *opencopy(char*); 30 Biobuf *opendump(char*); 31 char *qmail(char**, char*, int, Biobuf*); 32 void saveline(char*, char*, Resub*); 33 int optoutofspamfilter(char*); 34 35 void 36 usage(void) 37 { 38 fprint(2, "missing or bad arguments to qer\n"); 39 exits("usage"); 40 } 41 42 void * 43 Malloc(long n) 44 { 45 void *p; 46 47 p = malloc(n); 48 if(p == 0) 49 exits("malloc"); 50 return p; 51 } 52 53 void* 54 Realloc(void *p, ulong n) 55 { 56 p = realloc(p, n); 57 if(p == 0) 58 exits("realloc"); 59 return p; 60 } 61 62 void 63 main(int argc, char *argv[]) 64 { 65 int i, n, nolines, optout; 66 char **args, **a, *cp, *buf; 67 char body[Bodysize+2]; 68 Resub match[1]; 69 Biobuf *bp; 70 71 optout = 1; 72 a = args = Malloc((argc+1)*sizeof(char*)); 73 sprint(patfile, "%s/patterns", UPASLIB); 74 sprint(linefile, "%s/lines", UPASLOG); 75 sprint(holdqueue, "%s/queue.hold", SPOOL); 76 sprint(copydir, "%s/copy", SPOOL); 77 78 *a++ = argv[0]; 79 for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){ 80 switch(argv[0][1]){ 81 case 'c': /* save copy of message */ 82 cflag = 1; 83 break; 84 case 'd': /* debug */ 85 debug++; 86 *a++ = argv[0]; 87 break; 88 case 'h': /* queue held messages by sender domain */ 89 hflag = 1; /* -q flag must be set also */ 90 break; 91 case 'n': /* NOHOLD mode */ 92 nflag = 1; 93 break; 94 case 'p': /* pattern file */ 95 if(argv[0][2] || argv[1] == 0) 96 usage(); 97 argc--; 98 argv++; 99 strcpy(patfile, *argv); 100 break; 101 case 'q': /* queue name */ 102 if(argv[0][2] || argv[1] == 0) 103 usage(); 104 *a++ = argv[0]; 105 argc--; 106 argv++; 107 qname = a; 108 *a++ = argv[0]; 109 break; 110 case 's': /* save copy of dumped message */ 111 sflag = 1; 112 break; 113 case 't': /* test mode - don't log match 114 * and write message to /dev/null 115 */ 116 tflag = 1; 117 break; 118 case 'v': /* vebose - print matches */ 119 vflag = 1; 120 break; 121 default: 122 *a++ = argv[0]; 123 break; 124 } 125 } 126 127 if(argc < 3) 128 usage(); 129 130 Binit(&bin, 0, OREAD); 131 bp = Bopen(patfile, OREAD); 132 if(bp){ 133 parsepats(bp); 134 Bterm(bp); 135 } 136 qdir = a; 137 sender = argv[2]; 138 139 /* copy the rest of argv, acummulating the recipients as we go */ 140 for(i = 0; argv[i]; i++){ 141 *a++ = argv[i]; 142 if(i < 4) /* skip queue, 'mail', sender, dest sys */ 143 continue; 144 /* recipients and smtp flags - skip the latter*/ 145 if(strcmp(argv[i], "-g") == 0){ 146 *a++ = argv[++i]; 147 continue; 148 } 149 if(recips) 150 s_append(recips, ", "); 151 else 152 recips = s_new(); 153 s_append(recips, argv[i]); 154 if(optout && !optoutofspamfilter(argv[i])) 155 optout = 0; 156 } 157 *a = 0; 158 /* construct a command string for matching */ 159 snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips)); 160 cmd[sizeof(cmd)-1] = 0; 161 for(cp = cmd; *cp; cp++) 162 *cp = tolower(*cp); 163 164 /* canonicalize a copy of the header and body. 165 * buf points to orginal message and n contains 166 * number of bytes of original message read during 167 * canonicalization. 168 */ 169 *body = 0; 170 *header = 0; 171 buf = canon(&bin, header+1, body+1, &n); 172 if (buf == 0) 173 exits("read"); 174 175 /* if all users opt out, don't try matches */ 176 if(optout){ 177 if(cflag) 178 cout = opencopy(sender); 179 exits(qmail(args, buf, n, cout)); 180 } 181 182 /* Turn off line logging, if command line matches */ 183 nolines = matchaction(Lineoff, cmd, match); 184 185 for(i = 0; patterns[i].action; i++){ 186 /* Lineoff patterns were already done above */ 187 if(i == Lineoff) 188 continue; 189 /* don't apply "Line" patterns if excluded above */ 190 if(nolines && i == SaveLine) 191 continue; 192 /* apply patterns to the sender/recips, header and body */ 193 if(matchaction(i, cmd, match)) 194 break; 195 if(matchaction(i, header+1, match)) 196 break; 197 if(i == HoldHeader) 198 continue; 199 if(matchaction(i, body+1, match)) 200 break; 201 } 202 if(cflag && patterns[i].action == 0) /* no match found - save msg */ 203 cout = opencopy(sender); 204 205 exits(qmail(args, buf, n, cout)); 206 } 207 208 char* 209 qmail(char **argv, char *buf, int n, Biobuf *cout) 210 { 211 Waitmsg *status; 212 int i, pid, pipefd[2]; 213 char path[512]; 214 Biobuf *bp; 215 216 pid = 0; 217 if(tflag == 0){ 218 if(pipe(pipefd) < 0) 219 exits("pipe"); 220 pid = fork(); 221 if(pid == 0){ 222 dup(pipefd[0], 0); 223 for(i = sysfiles(); i >= 3; i--) 224 close(i); 225 snprint(path, sizeof(path), "%s/qer", UPASBIN); 226 *argv=path; 227 exec(path, argv); 228 exits("exec"); 229 } 230 Binit(&bout, pipefd[1], OWRITE); 231 bp = &bout; 232 } else 233 bp = Bopen("/dev/null", OWRITE); 234 235 while(n > 0){ 236 Bwrite(bp, buf, n); 237 if(cout) 238 Bwrite(cout, buf, n); 239 n = Bread(&bin, buf, sizeof(buf)-1); 240 } 241 Bterm(bp); 242 if(cout) 243 Bterm(cout); 244 if(tflag) 245 return 0; 246 247 close(pipefd[1]); 248 close(pipefd[0]); 249 for(;;){ 250 status = wait(); 251 if(status == nil || status->pid == pid) 252 break; 253 free(status); 254 } 255 if(status == nil) 256 strcpy(buf, "wait failed"); 257 else{ 258 strcpy(buf, status->msg); 259 free(status); 260 } 261 return buf; 262 } 263 264 char* 265 canon(Biobuf *bp, char *header, char *body, int *n) 266 { 267 int hsize; 268 char *raw; 269 270 hsize = 0; 271 *header = 0; 272 *body = 0; 273 raw = readmsg(bp, &hsize, n); 274 if(raw){ 275 if(convert(raw, raw+hsize, header, Hdrsize, 0)) 276 conv64(raw+hsize, raw+*n, body, Bodysize); /* base64 */ 277 else 278 convert(raw+hsize, raw+*n, body, Bodysize, 1); /* text */ 279 } 280 return raw; 281 } 282 283 int 284 matchaction(int action, char *message, Resub *m) 285 { 286 char *name; 287 Pattern *p; 288 289 if(message == 0 || *message == 0) 290 return 0; 291 292 name = patterns[action].action; 293 p = patterns[action].strings; 294 if(p) 295 if(matcher(name, p, message, m)) 296 return 1; 297 298 for(p = patterns[action].regexps; p; p = p->next) 299 if(matcher(name, p, message, m)) 300 return 1; 301 return 0; 302 } 303 304 int 305 matcher(char *action, Pattern *p, char *message, Resub *m) 306 { 307 char *cp; 308 String *s; 309 310 for(cp = message; matchpat(p, cp, m); cp = m->ep){ 311 switch(p->action){ 312 case SaveLine: 313 if(vflag) 314 xprint(2, action, m); 315 saveline(linefile, sender, m); 316 break; 317 case HoldHeader: 318 case Hold: 319 if(nflag) 320 continue; 321 if(vflag) 322 xprint(2, action, m); 323 *qdir = holdqueue; 324 if(hflag && qname){ 325 cp = strchr(sender, '!'); 326 if(cp){ 327 *cp = 0; 328 *qname = strdup(sender); 329 *cp = '!'; 330 } else 331 *qname = strdup(sender); 332 } 333 return 1; 334 case Dump: 335 if(vflag) 336 xprint(2, action, m); 337 *(m->ep) = 0; 338 if(!tflag){ 339 s = s_new(); 340 s_append(s, sender); 341 s = unescapespecial(s); 342 syslog(0, "smtpd", "Dumped %s [%s] to %s", s_to_c(s), m->sp, 343 s_to_c(s_restart(recips))); 344 s_free(s); 345 } 346 tflag = 1; 347 if(sflag) 348 cout = opendump(sender); 349 return 1; 350 default: 351 break; 352 } 353 } 354 return 0; 355 } 356 357 void 358 saveline(char *file, char *sender, Resub *rp) 359 { 360 char *p, *q; 361 int i, c; 362 Biobuf *bp; 363 364 if(rp->sp == 0 || rp->ep == 0) 365 return; 366 /* back up approx 20 characters to whitespace */ 367 for(p = rp->sp, i = 0; *p && i < 20; i++, p--) 368 ; 369 while(*p && *p != ' ') 370 p--; 371 p++; 372 373 /* grab about 20 more chars beyond the end of the match */ 374 for(q = rp->ep, i = 0; *q && i < 20; i++, q++) 375 ; 376 while(*q && *q != ' ') 377 q++; 378 379 c = *q; 380 *q = 0; 381 bp = sysopen(file, "al", 0644); 382 if(bp){ 383 Bprint(bp, "%s-> %s\n", sender, p); 384 Bterm(bp); 385 } 386 else if(debug) 387 fprint(2, "can't save line: (%s) %s\n", sender, p); 388 *q = c; 389 } 390 391 Biobuf* 392 opendump(char *sender) 393 { 394 int i; 395 ulong h; 396 char buf[512]; 397 Biobuf *b; 398 char *cp; 399 400 cp = ctime(time(0)); 401 cp[7] = 0; 402 cp[10] = 0; 403 if(cp[8] == ' ') 404 sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]); 405 else 406 sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]); 407 cp = buf+strlen(buf); 408 if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){ 409 syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender); 410 return 0; 411 } 412 413 h = 0; 414 while(*sender) 415 h = h*257 + *sender++; 416 for(i = 0; i < 50; i++){ 417 h += lrand(); 418 sprint(cp, "/%lud", h); 419 b = sysopen(buf, "wlc", 0644); 420 if(b){ 421 if(vflag) 422 fprint(2, "saving in %s\n", buf); 423 return b; 424 } 425 } 426 return 0; 427 } 428 429 Biobuf* 430 opencopy(char *sender) 431 { 432 int i; 433 ulong h; 434 char buf[512]; 435 Biobuf *b; 436 437 h = 0; 438 while(*sender) 439 h = h*257 + *sender++; 440 for(i = 0; i < 50; i++){ 441 h += lrand(); 442 sprint(buf, "%s/%lud", copydir, h); 443 b = sysopen(buf, "wlc", 0600); 444 if(b) 445 return b; 446 } 447 return 0; 448 } 449 450 int 451 optoutofspamfilter(char *addr) 452 { 453 char *p, *f; 454 int rv; 455 456 p = strchr(addr, '!'); 457 if(p) 458 p++; 459 else 460 p = addr; 461 462 rv = 0; 463 f = smprint("/mail/box/%s/nospamfiltering", p); 464 if(f != nil){ 465 rv = access(f, 0)==0; 466 free(f); 467 } 468 469 return rv; 470 } 471