aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/upas/smtp/spam.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2005-10-29 16:26:44 +0000
committerrsc <devnull@localhost>2005-10-29 16:26:44 +0000
commit5cdb17983ae6e6367ad7a940cb219eab247a9304 (patch)
tree8ca1ef49af2a96e7daebe624d91fdf679814a057 /src/cmd/upas/smtp/spam.c
parentcd3745196389579fb78b9b01ef1daefb5a57aa71 (diff)
downloadplan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.gz
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.tar.bz2
plan9port-5cdb17983ae6e6367ad7a940cb219eab247a9304.zip
Thanks to John Cummings.
Diffstat (limited to 'src/cmd/upas/smtp/spam.c')
-rw-r--r--src/cmd/upas/smtp/spam.c591
1 files changed, 591 insertions, 0 deletions
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 <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)
+{
+ 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 \<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 = 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;
+}