#include "common.h" #include "spam.h" int cflag; int debug; int hflag; int nflag; int sflag; int tflag; int vflag; Biobuf bin, bout, *cout; /* file names */ char patfile[128]; char linefile[128]; char holdqueue[128]; char copydir[128]; char header[Hdrsize+2]; char cmd[1024]; char **qname; char **qdir; char *sender; String *recips; char* canon(Biobuf*, char*, char*, int*); int matcher(char*, Pattern*, char*, Resub*); int matchaction(int, char*, Resub*); Biobuf *opencopy(char*); Biobuf *opendump(char*); char *qmail(char**, char*, int, Biobuf*); void saveline(char*, char*, Resub*); int optoutofspamfilter(char*); void usage(void) { fprint(2, "missing or bad arguments to qer\n"); exits("usage"); } void regerror(char *s) { fprint(2, "scanmail: %s\n", s); } void * Malloc(long n) { void *p; p = malloc(n); if(p == 0) exits("malloc"); return p; } void* Realloc(void *p, ulong n) { p = realloc(p, n); if(p == 0) exits("realloc"); return p; } void main(int argc, char *argv[]) { int i, n, nolines, optout; char **args, **a, *cp, *buf; char body[Bodysize+2]; Resub match[1]; Biobuf *bp; optout = 1; a = args = Malloc((argc+1)*sizeof(char*)); sprint(patfile, "%s/patterns", UPASLIB); sprint(linefile, "%s/lines", UPASLOG); sprint(holdqueue, "%s/queue.hold", SPOOL); sprint(copydir, "%s/copy", SPOOL); *a++ = argv[0]; for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){ switch(argv[0][1]){ case 'c': /* save copy of message */ cflag = 1; break; case 'd': /* debug */ debug++; *a++ = argv[0]; break; case 'h': /* queue held messages by sender domain */ hflag = 1; /* -q flag must be set also */ break; case 'n': /* NOHOLD mode */ nflag = 1; break; case 'p': /* pattern file */ if(argv[0][2] || argv[1] == 0) usage(); argc--; argv++; strecpy(patfile, patfile+sizeof patfile, *argv); break; case 'q': /* queue name */ if(argv[0][2] || argv[1] == 0) usage(); *a++ = argv[0]; argc--; argv++; qname = a; *a++ = argv[0]; break; case 's': /* save copy of dumped message */ sflag = 1; break; case 't': /* test mode - don't log match * and write message to /dev/null */ tflag = 1; break; case 'v': /* vebose - print matches */ vflag = 1; break; default: *a++ = argv[0]; break; } } if(argc < 3) usage(); Binit(&bin, 0, OREAD); bp = Bopen(patfile, OREAD); if(bp){ parsepats(bp); Bterm(bp); } qdir = a; sender = argv[2]; /* copy the rest of argv, acummulating the recipients as we go */ for(i = 0; argv[i]; i++){ *a++ = argv[i]; if(i < 4) /* skip queue, 'mail', sender, dest sys */ continue; /* recipients and smtp flags - skip the latter*/ if(strcmp(argv[i], "-g") == 0){ *a++ = argv[++i]; continue; } if(recips) s_append(recips, ", "); else recips = s_new(); s_append(recips, argv[i]); if(optout && !optoutofspamfilter(argv[i])) optout = 0; } *a = 0; /* construct a command string for matching */ snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips)); cmd[sizeof(cmd)-1] = 0; for(cp = cmd; *cp; cp++) *cp = tolower(*cp); /* canonicalize a copy of the header and body. * buf points to orginal message and n contains * number of bytes of original message read during * canonicalization. */ *body = 0; *header = 0; buf = canon(&bin, header+1, body+1, &n); if (buf == 0) exits("read"); /* if all users opt out, don't try matches */ if(optout){ if(cflag) cout = opencopy(sender); exits(qmail(args, buf, n, cout)); } /* Turn off line logging, if command line matches */ nolines = matchaction(Lineoff, cmd, match); for(i = 0; patterns[i].action; i++){ /* Lineoff patterns were already done above */ if(i == Lineoff) continue; /* don't apply "Line" patterns if excluded above */ if(nolines && i == SaveLine) continue; /* apply patterns to the sender/recips, header and body */ if(matchaction(i, cmd, match)) break; if(matchaction(i, header+1, match)) break; if(i == HoldHeader) continue; if(matchaction(i, body+1, match)) break; } if(cflag && patterns[i].action == 0) /* no match found - save msg */ cout = opencopy(sender); exits(qmail(args, buf, n, cout)); } char* qmail(char **argv, char *buf, int n, Biobuf *cout) { Waitmsg *status; int i, pid, pipefd[2]; char path[512]; Biobuf *bp; pid = 0; if(tflag == 0){ if(pipe(pipefd) < 0) exits("pipe"); pid = fork(); if(pid == 0){ dup(pipefd[0], 0); for(i = sysfiles(); i >= 3; i--) close(i); snprint(path, sizeof(path), "%s/qer", UPASBIN); *argv=path; exec(path, argv); exits("exec"); } Binit(&bout, pipefd[1], OWRITE); bp = &bout; } else bp = Bopen("/dev/null", OWRITE); while(n > 0){ Bwrite(bp, buf, n); if(cout) Bwrite(cout, buf, n); n = Bread(&bin, buf, sizeof(buf)-1); } Bterm(bp); if(cout) Bterm(cout); if(tflag) return 0; close(pipefd[1]); close(pipefd[0]); for(;;){ status = wait(); if(status == nil || status->pid == pid) break; free(status); } if(status == nil) strcpy(buf, "wait failed"); else{ strcpy(buf, status->msg); free(status); } return buf; } char* canon(Biobuf *bp, char *header, char *body, int *n) { int hsize; char *raw; hsize = 0; *header = 0; *body = 0; raw = readmsg(bp, &hsize, n); if(raw){ if(convert(raw, raw+hsize, header, Hdrsize, 0)) conv64(raw+hsize, raw+*n, body, Bodysize); /* base64 */ else convert(raw+hsize, raw+*n, body, Bodysize, 1); /* text */ } return raw; } int matchaction(int action, char *message, Resub *m) { char *name; Pattern *p; if(message == 0 || *message == 0) return 0; name = patterns[action].action; p = patterns[action].strings; if(p) if(matcher(name, p, message, m)) return 1; for(p = patterns[action].regexps; p; p = p->next) if(matcher(name, p, message, m)) return 1; return 0; } int matcher(char *action, Pattern *p, char *message, Resub *m) { char *cp; String *s; for(cp = message; matchpat(p, cp, m); cp = m->ep){ switch(p->action){ case SaveLine: if(vflag) xprint(2, action, m); saveline(linefile, sender, m); break; case HoldHeader: case Hold: if(nflag) continue; if(vflag) xprint(2, action, m); *qdir = holdqueue; if(hflag && qname){ cp = strchr(sender, '!'); if(cp){ *cp = 0; *qname = strdup(sender); *cp = '!'; } else *qname = strdup(sender); } return 1; case Dump: if(vflag) xprint(2, action, m); *(m->ep) = 0; if(!tflag){ s = s_new(); s_append(s, sender); s = unescapespecial(s); syslog(0, "smtpd", "Dumped %s [%s] to %s", s_to_c(s), m->sp, s_to_c(s_restart(recips))); s_free(s); } tflag = 1; if(sflag) cout = opendump(sender); return 1; default: break; } } return 0; } void saveline(char *file, char *sender, Resub *rp) { char *p, *q; int i, c; Biobuf *bp; if(rp->sp == 0 || rp->ep == 0) return; /* back up approx 20 characters to whitespace */ for(p = rp->sp, i = 0; *p && i < 20; i++, p--) ; while(*p && *p != ' ') p--; p++; /* grab about 20 more chars beyond the end of the match */ for(q = rp->ep, i = 0; *q && i < 20; i++, q++) ; while(*q && *q != ' ') q++; c = *q; *q = 0; bp = sysopen(file, "al", 0644); if(bp){ Bprint(bp, "%s-> %s\n", sender, p); Bterm(bp); } else if(debug) fprint(2, "can't save line: (%s) %s\n", sender, p); *q = c; } Biobuf* opendump(char *sender) { int i; ulong h; char buf[512]; Biobuf *b; char *cp; cp = ctime(time(0)); cp[7] = 0; cp[10] = 0; if(cp[8] == ' ') sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]); else sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]); cp = buf+strlen(buf); if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){ syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender); return 0; } h = 0; while(*sender) h = h*257 + *sender++; for(i = 0; i < 50; i++){ h += lrand(); sprint(cp, "/%lud", h); b = sysopen(buf, "wlc", 0644); if(b){ if(vflag) fprint(2, "saving in %s\n", buf); return b; } } return 0; } Biobuf* opencopy(char *sender) { int i; ulong h; char buf[512]; Biobuf *b; h = 0; while(*sender) h = h*257 + *sender++; for(i = 0; i < 50; i++){ h += lrand(); sprint(buf, "%s/%lud", copydir, h); b = sysopen(buf, "wlc", 0600); if(b) return b; } return 0; } int optoutofspamfilter(char *addr) { char *p, *f; int rv; p = strchr(addr, '!'); if(p) p++; else p = addr; rv = 0; f = smprint("/mail/box/%s/nospamfiltering", p); if(f != nil){ rv = access(f, 0)==0; free(f); } return rv; }