ref: 40d6302b5f289ad8a617d12fa911197dddafc634
dir: /sys/src/cmd/upas/smtp/spam.c/
#include "common.h" #include "smtpd.h" #include <ip.h> 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) { static 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[Pathlen]; 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 \<newline> */ 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 = create(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; }