From 5cdb17983ae6e6367ad7a940cb219eab247a9304 Mon Sep 17 00:00:00 2001 From: rsc Date: Sat, 29 Oct 2005 16:26:44 +0000 Subject: Thanks to John Cummings. --- src/cmd/upas/smtp/spam.c | 591 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 591 insertions(+) create mode 100644 src/cmd/upas/smtp/spam.c (limited to 'src/cmd/upas/smtp/spam.c') diff --git a/src/cmd/upas/smtp/spam.c b/src/cmd/upas/smtp/spam.c new file mode 100644 index 00000000..84a8fccc --- /dev/null +++ b/src/cmd/upas/smtp/spam.c @@ -0,0 +1,591 @@ +#include "common.h" +#include "smtpd.h" +#include + +enum { + NORELAY = 0, + DNSVERIFY, + SAVEBLOCK, + DOMNAME, + OURNETS, + OURDOMS, + + IP = 0, + STRING, +}; + + +typedef struct Keyword Keyword; + +struct Keyword { + char *name; + int code; +}; + +static Keyword options[] = { + "norelay", NORELAY, + "verifysenderdom", DNSVERIFY, + "saveblockedmsg", SAVEBLOCK, + "defaultdomain", DOMNAME, + "ournets", OURNETS, + "ourdomains", OURDOMS, + 0, NONE, +}; + +static Keyword actions[] = { + "allow", ACCEPT, + "block", BLOCKED, + "deny", DENIED, + "dial", DIALUP, + "delay", DELAY, + 0, NONE, +}; + +static int hisaction; +static List ourdoms; +static List badguys; +static ulong v4peerip; + +static char* getline(Biobuf*); +static int cidrcheck(char*); + +static int +findkey(char *val, Keyword *p) +{ + + for(; p->name; p++) + if(strcmp(val, p->name) == 0) + break; + return p->code; +} + +char* +actstr(int a) +{ + char buf[32]; + Keyword *p; + + for(p=actions; p->name; p++) + if(p->code == a) + return p->name; + if(a==NONE) + return "none"; + sprint(buf, "%d", a); + return buf; +} + +int +getaction(char *s, char *type) +{ + char buf[1024]; + Keyword *k; + + if(s == nil || *s == 0) + return ACCEPT; + + for(k = actions; k->name != 0; k++){ + snprint(buf, sizeof buf, "/mail/ratify/%s/%s/%s", k->name, type, s); + if(access(buf,0) >= 0) + return k->code; + } + return ACCEPT; +} + +int +istrusted(char *s) +{ + char buf[1024]; + + if(s == nil || *s == 0) + return 0; + + snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s); + return access(buf,0) >= 0; +} + +void +getconf(void) +{ + Biobuf *bp; + char *cp, *p; + String *s; + char buf[512]; + uchar addr[4]; + + v4parseip(addr, nci->rsys); + v4peerip = nhgetl(addr); + + trusted = istrusted(nci->rsys); + hisaction = getaction(nci->rsys, "ip"); + if(debug){ + fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted); + fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction)); + } + snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB); + bp = sysopen(buf, "r", 0); + if(bp == 0) + return; + + for(;;){ + cp = getline(bp); + if(cp == 0) + break; + p = cp+strlen(cp)+1; + switch(findkey(cp, options)){ + case NORELAY: + if(fflag == 0 && strcmp(p, "on") == 0) + fflag++; + break; + case DNSVERIFY: + if(rflag == 0 && strcmp(p, "on") == 0) + rflag++; + break; + case SAVEBLOCK: + if(sflag == 0 && strcmp(p, "on") == 0) + sflag++; + break; + case DOMNAME: + if(dom == 0) + dom = strdup(p); + break; + case OURNETS: + if (trusted == 0) + trusted = cidrcheck(p); + break; + case OURDOMS: + while(*p){ + s = s_new(); + s_append(s, p); + listadd(&ourdoms, s); + p += strlen(p)+1; + } + break; + default: + break; + } + } + sysclose(bp); +} + +/* + * match a user name. the only meta-char is '*' which matches all + * characters. we only allow it as "*", which matches anything or + * an * at the end of the name (e.g., "username*") which matches + * trailing characters. + */ +static int +usermatch(char *pathuser, char *specuser) +{ + int n; + + n = strlen(specuser)-1; + if(specuser[n] == '*'){ + if(n == 0) /* match everything */ + return 0; + return strncmp(pathuser, specuser, n); + } + return strcmp(pathuser, specuser); +} + +static int +dommatch(char *pathdom, char *specdom) +{ + int n; + + if (*specdom == '*'){ + if (specdom[1] == '.' && specdom[2]){ + specdom += 2; + n = strlen(pathdom)-strlen(specdom); + if(n == 0 || (n > 0 && pathdom[n-1] == '.')) + return strcmp(pathdom+n, specdom); + return n; + } + } + return strcmp(pathdom, specdom); +} + +/* + * figure out action for this sender + */ +int +blocked(String *path) +{ + String *lpath; + int action; + + if(debug) + fprint(2, "blocked(%s)\n", s_to_c(path)); + + /* if the sender's IP address is blessed, ignore sender email address */ + if(trusted){ + if(debug) + fprint(2, "\ttrusted => trusted\n"); + return TRUSTED; + } + + /* if sender's IP address is blocked, ignore sender email address */ + if(hisaction != ACCEPT){ + if(debug) + fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction)); + return hisaction; + } + + /* convert to lower case */ + lpath = s_copy(s_to_c(path)); + s_tolower(lpath); + + /* classify */ + action = getaction(s_to_c(lpath), "account"); + if(debug) + fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action)); + s_free(lpath); + return action; +} + +/* + * get a canonicalized line: a string of null-terminated lower-case + * tokens with a two null bytes at the end. + */ +static char* +getline(Biobuf *bp) +{ + char c, *cp, *p, *q; + int n; + + static char *buf; + static int bufsize; + + for(;;){ + cp = Brdline(bp, '\n'); + if(cp == 0) + return 0; + n = Blinelen(bp); + cp[n-1] = 0; + if(buf == 0 || bufsize < n+1){ + bufsize += 512; + if(bufsize < n+1) + bufsize = n+1; + buf = realloc(buf, bufsize); + if(buf == 0) + break; + } + q = buf; + for (p = cp; *p; p++){ + c = *p; + if(c == '\\' && p[1]) /* we don't allow \ */ + c = *++p; + else + if(c == '#') + break; + else + if(c == ' ' || c == '\t' || c == ',') + if(q == buf || q[-1] == 0) + continue; + else + c = 0; + *q++ = tolower(c); + } + if(q != buf){ + if(q[-1]) + *q++ = 0; + *q = 0; + break; + } + } + return buf; +} + +static int +isourdom(char *s) +{ + Link *l; + + if(strchr(s, '.') == nil) + return 1; + + for(l = ourdoms.first; l; l = l->next){ + if(dommatch(s, s_to_c(l->p)) == 0) + return 1; + } + return 0; +} + +int +forwarding(String *path) +{ + char *cp, *s; + String *lpath; + + if(debug) + fprint(2, "forwarding(%s)\n", s_to_c(path)); + + /* first check if they want loopback */ + lpath = s_copy(s_to_c(s_restart(path))); + if(nci->rsys && *nci->rsys){ + cp = s_to_c(lpath); + if(strncmp(cp, "[]!", 3) == 0){ +found: + s_append(path, "["); + s_append(path, nci->rsys); + s_append(path, "]!"); + s_append(path, cp+3); + s_terminate(path); + s_free(lpath); + return 0; + } + cp = strchr(cp,'!'); /* skip our domain and check next */ + if(cp++ && strncmp(cp, "[]!", 3) == 0) + goto found; + } + + /* if mail is from a trusted IP addr, allow it to forward */ + if(trusted) { + s_free(lpath); + return 0; + } + + /* sender is untrusted; ensure receiver is in one of our domains */ + for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */ + *cp = tolower(*cp); + + for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){ + *cp = 0; + if(!isourdom(s)){ + s_free(lpath); + return 1; + } + } + s_free(lpath); + return 0; +} + +int +masquerade(String *path, char *him) +{ + char *cp, *s; + String *lpath; + int rv = 0; + + if(debug) + fprint(2, "masquerade(%s)\n", s_to_c(path)); + + if(trusted) + return 0; + if(path == nil) + return 0; + + lpath = s_copy(s_to_c(path)); + + /* sender is untrusted; ensure receiver is in one of our domains */ + for(cp = s_to_c(lpath); *cp; cp++) /* convert receiver lc */ + *cp = tolower(*cp); + s = s_to_c(lpath); + + /* scan first element of ! or last element of @ paths */ + if((cp = strchr(s, '!')) != nil){ + *cp = 0; + if(isourdom(s)) + rv = 1; + } else if((cp = strrchr(s, '@')) != nil){ + if(isourdom(cp+1)) + rv = 1; + } else { + if(isourdom(him)) + rv = 1; + } + + s_free(lpath); + return rv; +} + +/* this is a v4 only check */ +static int +cidrcheck(char *cp) +{ + char *p; + ulong a, m; + uchar addr[IPv4addrlen]; + uchar mask[IPv4addrlen]; + + if(v4peerip == 0) + return 0; + + /* parse a list of CIDR addresses comparing each to the peer IP addr */ + while(cp && *cp){ + v4parsecidr(addr, mask, cp); + a = nhgetl(addr); + m = nhgetl(mask); + /* + * if a mask isn't specified, we build a minimal mask + * instead of using the default mask for that net. in this + * case we never allow a class A mask (0xff000000). + */ + if(strchr(cp, '/') == 0){ + m = 0xff000000; + p = cp; + for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.')) + m = (m>>8)|0xff000000; + + /* force at least a class B */ + m |= 0xffff0000; + } + if((v4peerip&m) == a) + return 1; + cp += strlen(cp)+1; + } + return 0; +} + +int +isbadguy(void) +{ + Link *l; + + /* check if this IP address is banned */ + for(l = badguys.first; l; l = l->next) + if(cidrcheck(s_to_c(l->p))) + return 1; + + return 0; +} + +void +addbadguy(char *p) +{ + listadd(&badguys, s_copy(p)); +}; + +char* +dumpfile(char *sender) +{ + int i, fd; + ulong h; + static char buf[512]; + char *cp; + + if (sflag == 1){ + cp = ctime(time(0)); + cp[7] = 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) + return "/dev/null"; + h = 0; + while(*sender) + h = h*257 + *sender++; + for(i = 0; i < 50; i++){ + h += lrand(); + sprint(cp, "/%lud", h); + if(access(buf, 0) >= 0) + continue; + fd = syscreate(buf, ORDWR, 0666); + if(fd >= 0){ + if(debug) + fprint(2, "saving in %s\n", buf); + close(fd); + return buf; + } + } + } + return "/dev/null"; +} + +char *validator = "/mail/lib/validateaddress"; + +int +recipok(char *user) +{ + char *cp, *p, c; + char buf[512]; + int n; + Biobuf *bp; + int pid; + Waitmsg *w; + + if(shellchars(user)){ + syslog(0, "smtpd", "shellchars in user name"); + return 0; + } + + if(access(validator, AEXEC) == 0) + switch(pid = fork()) { + case -1: + break; + case 0: + execl(validator, "validateaddress", user, nil); + exits(0); + default: + while(w = wait()) { + if(w->pid != pid) + continue; + if(w->msg[0] != 0){ + /* + syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg); + */ + return 0; + } + break; + } + } + + snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB); + bp = sysopen(buf, "r", 0); + if(bp == 0) + return 1; + for(;;){ + cp = Brdline(bp, '\n'); + if(cp == 0) + break; + n = Blinelen(bp); + cp[n-1] = 0; + + while(*cp == ' ' || *cp == '\t') + cp++; + for(p = cp; c = *p; p++){ + if(c == '#') + break; + if(c == ' ' || c == '\t') + break; + } + if(p > cp){ + *p = 0; + if(cistrcmp(user, cp) == 0){ + syslog(0, "smtpd", "names.blocked blocks %s", user); + Bterm(bp); + return 0; + } + } + } + Bterm(bp); + return 1; +} + +/* + * a user can opt out of spam filtering by creating + * a file in his mail directory named 'nospamfiltering'. + */ +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; +} -- cgit v1.2.3