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