diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2017-03-12 17:15:03 +0100 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2017-03-12 17:15:03 +0100 |
commit | 963cfc9a6f6e721f52aa949e6d1af0c3e8dc2ecc (patch) | |
tree | 749b74875dbc49bcf6ed0776648b8f0ef9417407 /sys/src/cmd/upas/spf | |
parent | 8177d20fb2709ba9290dfd41308b8e5bee4e00f8 (diff) |
merging erik quanstros nupas
Diffstat (limited to 'sys/src/cmd/upas/spf')
-rw-r--r-- | sys/src/cmd/upas/spf/dns.c | 81 | ||||
-rw-r--r-- | sys/src/cmd/upas/spf/macro.c | 304 | ||||
-rw-r--r-- | sys/src/cmd/upas/spf/mkfile | 14 | ||||
-rw-r--r-- | sys/src/cmd/upas/spf/mtest.c | 39 | ||||
-rw-r--r-- | sys/src/cmd/upas/spf/spf.c | 800 | ||||
-rw-r--r-- | sys/src/cmd/upas/spf/spf.h | 12 | ||||
-rw-r--r-- | sys/src/cmd/upas/spf/testsuite | 9 |
7 files changed, 1259 insertions, 0 deletions
diff --git a/sys/src/cmd/upas/spf/dns.c b/sys/src/cmd/upas/spf/dns.c new file mode 100644 index 000000000..01b2b2c43 --- /dev/null +++ b/sys/src/cmd/upas/spf/dns.c @@ -0,0 +1,81 @@ +#include "spf.h" + +extern char dflag; +extern char vflag; +extern char *netroot; + +static int +timeout(void*, char *msg) +{ + if(strstr(msg, "alarm")){ + fprint(2, "deferred: dns timeout"); + exits("deferred: dns timeout"); + } + return 0; +} + +static Ndbtuple* +tdnsquery(char *r, char *s, char *v) +{ + long a; + Ndbtuple *t; + + atnotify(timeout, 1); + a = alarm(15*1000); + t = dnsquery(r, s, v); + alarm(a); + atnotify(timeout, 0); + return t; +} + +Ndbtuple* +vdnsquery(char *s, char *v, int recur) +{ + Ndbtuple *n, *t; + static int nquery; + + /* conflicts with standard: must limit to 10 and -> fail */ + if(recur > 5 || ++nquery == 25){ + fprint(2, "dns query limited %d %d\n", recur, nquery); + return 0; + } + if(dflag) + fprint(2, "dnsquery(%s, %s, %s) ->\n", netroot, s, v); + t = tdnsquery(netroot, s, v); + if(dflag) + for(n = t; n; n = n->entry) + fprint(2, "\t%s\t%s\n", n->attr, n->val); + return t; +} + +void +dnreverse(char *s, int l, char *d) +{ + char *p, *e, buf[100], *f[15]; + int i, n; + + n = getfields(d, f, nelem(f), 0, "."); + p = e = buf; + if(l < sizeof buf) + e += l; + else + e += sizeof buf; + for(i = 1; i <= n; i++) + p = seprint(p, e, "%s.", f[n-i]); + if(p > buf) + p = seprint(p-1, e, ".in-addr.arpa"); + memmove(s, buf, p-buf+1); +} + +int +dncontains(char *d, char *s) +{ +loop: + if(!strcmp(d, s)) + return 1; + if(!(s = strchr(s, '.'))) + return 0; + s++; + goto loop; +} + diff --git a/sys/src/cmd/upas/spf/macro.c b/sys/src/cmd/upas/spf/macro.c new file mode 100644 index 000000000..2ccc8e3b8 --- /dev/null +++ b/sys/src/cmd/upas/spf/macro.c @@ -0,0 +1,304 @@ +#include "spf.h" + +#define mrprint(...) snprint(m->mreg, sizeof m->mreg, __VA_ARGS__) + +typedef struct Mfmt Mfmt; +typedef struct Macro Macro; + +struct Mfmt{ + char buf[0xff]; + char *p; + char *e; + + char mreg[0xff]; + int f1; + int f2; + int f3; + + char *sender; + char *domain; + char *ip; + char *helo; + uchar ipa[IPaddrlen]; +}; + +struct Macro{ + char c; + void (*f)(Mfmt*); +}; + +static void +ms(Mfmt *m) +{ + mrprint("%s", m->sender); +} + +static void +ml(Mfmt *m) +{ + char *p; + + mrprint("%s", m->sender); + if(p = strchr(m->mreg, '@')) + *p = 0; +} + +static void +mo(Mfmt *m) +{ + mrprint("%s", m->domain); +} + +static void +md(Mfmt *m) +{ + mrprint("%s", m->domain); +} + +static void +mi(Mfmt *m) +{ + uint i, c; + + if(isv4(m->ipa)) + mrprint("%s", m->ip); + else{ + for(i = 0; i < 32; i++){ + c = m->ipa[i / 2]; + if((i & 1) == 0) + c >>= 4; + sprint(m->mreg+2*i, "%ux.", c & 0xf); + } + m->mreg[2*32 - 1] = 0; + } +} + +static int +maquery(Mfmt *m, char *d, char *match, int recur) +{ + int r; + Ndbtuple *t, *n; + + r = 0; + t = vdnsquery(d, "any", recur); + for(n = t; n; n = n->entry) + if(!strcmp(n->attr, "ip") || !strcmp(n->attr, "ipv6")){ + if(!strcmp(n->val, match)){ + r = 1; + break; + } + }else if(!strcmp(n->attr, "cname")) + maquery(m, d, match, recur+1); + ndbfree(t); + return r; +} + +static int +lrcmp(char *a, char *b) +{ + return strlen(b) - strlen(a); +} + +static void +mptrquery(Mfmt *m, char *d, int recur) +{ + char *s, buf[64], *a, *list[11]; + int nlist, i; + Ndbtuple *t, *n; + + nlist = 0; + dnreverse(buf, sizeof buf, s = strdup(m->ip)); + t = vdnsquery(buf, "ptr", recur); + for(n = t; n; n = n->entry){ + if(!strcmp(n->attr, "dom") || !strcmp(n->attr, "cname")) + if(dncontains(n->val, d) && maquery(m, n->val, m->ip, recur+1)) + list[nlist++] = strdup(n->val); + } + ndbfree(t); + free(s); + qsort(list, nlist, sizeof *list, (int(*)(void*,void*))lrcmp); + a = "unknown"; + for(i = 0; i < nlist; i++) + if(!strcmp(list[i], d)){ + a = list[i]; + break; + }else if(dncontains(list[i], d)) + a = list[i]; + mrprint("%s", a); + for(i = 0; i < nlist; i++) + free(list[i]); +} + +static void +mp(Mfmt *m) +{ + /* + * we're supposed to do a reverse lookup on the ip & compare. + * this is a very bad idea. + */ +// mrprint("unknown); /* simulate dns failure */ + mptrquery(m, m->domain, 0); +} + +static void +mv(Mfmt *m) +{ + if(isv4(m->ipa)) + mrprint("in-addr"); + else + mrprint("ip6"); +} + +static void +mh(Mfmt *m) +{ + mrprint("%s", m->helo); +} + +static Macro tab[] = { +'s', ms, /* sender */ +'l', ml, /* local part of sender */ +'o', mo, /* domain of sender */ +'d', md, /* domain */ +'i', mi, /* ip */ +'p', mp, /* validated domain name of ip */ +'v', mv, /* "in-addr" if ipv4, or "ip6" if ipv6 */ +'h', mh, /* helo/ehol domain */ +}; + +static void +reverse(Mfmt *m) +{ + char *p, *e, buf[100], *f[32], sep[2]; + int i, n; + + sep[0] = m->f2; + sep[1] = 0; + n = getfields(m->mreg, f, nelem(f), 0, sep); + p = e = buf; + e += sizeof buf-1; + for(i = 0; i < n; i++) + p = seprint(p, e, "%s.", f[n-i-1]); + if(p > buf) + p--; + *p = 0; + memmove(m->mreg, buf, p-buf+1); + m->f2 = '.'; +} + +static void +chop(Mfmt *m) +{ + char *p, *e, buf[100], *f[32], sep[2]; + int i, n; + + sep[0] = m->f2; + sep[1] = 0; + n = getfields(m->mreg, f, nelem(f), 0, sep); + p = e = buf; + e += sizeof buf-1; + if(m->f1 == 0) + i = 0; + else + i = n-m->f1; + if(i < 0) + i = 0; + for(; i < n; i++) + p = seprint(p, e, "%s.", f[i]); + if(p > buf) + p--; + *p = 0; + memmove(m->mreg, buf, p-buf+1); + m->f2 = '.'; +} + +static void +mfmtinit(Mfmt *m, char *s, char *d, char *h, char *i) +{ + memset(m, 0, sizeof *m); + m->p = m->buf; + m->e = m->p + sizeof m->buf-1; + m->sender = s? s: "Unsets"; + m->domain = d? d: "Unsetd"; + m->helo = h? h: "Unseth"; + m->ip = i? i: "127.0.0.2"; + parseip(m->ipa, m->ip); +} + +/* url escaping? rfc3986 */ +static void +mputc(Mfmt *m, int c) +{ + if(m->p < m->e) + *m->p++ = c; +} + +static void +mputs(Mfmt *m, char *s) +{ + int c; + + while(c = *s++) + mputc(m, c); +} + +char* +macro(char *f, char *sender, char *dom, char *hdom, char *ip) +{ + char *p; + int i, c; + Mfmt m; + + mfmtinit(&m, sender, dom, hdom, ip); + while(*f){ + while((c = *f++) && c != '%') + mputc(&m, c); + if(c == 0) + break; + switch(*f++){ + case '%': + mputc(&m, '%'); + break; + case '-': + mputs(&m, "%20"); + break; + case '_': + mputc(&m, ' '); + break; + case '{': + m.f1 = 0; + m.f2 = '.'; + m.f3 = 0; + c = *f++; + if(c >= 'A' && c <= 'Z') + c += 0x20; + for(i = 0; i < nelem(tab); i++) + if(tab[i].c == c) + break; + if(i == nelem(tab)) + return 0; + for(c = *f++; c >= '0' && c <= '9'; c = *f++) + m.f1 = m.f1*10 + c-'0'; + if(c == 'R' || c == 'r'){ + m.f3 = 'r'; + c = *f++; + } + for(; p = strchr(".-+,_=", c); c = *f++) + m.f2 = *p; + if(c == '}'){ + tab[i].f(&m); + if(m.f1 || m.f2 != '.') + chop(&m); + if(m.f3 == 'r') + reverse(&m); + mputs(&m, m.mreg); + m.mreg[0] = 0; + break; + } + default: + return 0; + } + } + mputc(&m, 0); + return strdup(m.buf); +} diff --git a/sys/src/cmd/upas/spf/mkfile b/sys/src/cmd/upas/spf/mkfile new file mode 100644 index 000000000..f612714f9 --- /dev/null +++ b/sys/src/cmd/upas/spf/mkfile @@ -0,0 +1,14 @@ +</$objtype/mkfile +<../mkupas + +TARG=spf + +OFILES=\ + dns.$O\ + macro.$O\ + spf.$O\ + +</sys/src/cmd/mkone + +mtest: dns.$O macro.$O mtest.$O + $LD $LDFLAGS -o $target $prereq diff --git a/sys/src/cmd/upas/spf/mtest.c b/sys/src/cmd/upas/spf/mtest.c new file mode 100644 index 000000000..3bd0e14f2 --- /dev/null +++ b/sys/src/cmd/upas/spf/mtest.c @@ -0,0 +1,39 @@ +#include "spf.h" + +char dflag; +char vflag; +char *netroot = "/net"; + +void +usage(void) +{ + fprint(2, "usage: mtest [-dv] sender dom hello ip\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *a[5], *s; + int i; + + ARGBEGIN{ + case 'd': + dflag = 1; + break; + case 'v': + vflag = 1; + break; + default: + usage(); + }ARGEND + + fmtinstall('I', eipfmt); + memset(a, 0, sizeof a); + for(i = 0; i < argc && i < nelem(a); i++) + a[i] = argv[i]; + s = macro(a[0], a[1], a[2], a[3], a[4]); + print("%s\n", s); + free(s); + exits(""); +} diff --git a/sys/src/cmd/upas/spf/spf.c b/sys/src/cmd/upas/spf/spf.c new file mode 100644 index 000000000..b76db76a2 --- /dev/null +++ b/sys/src/cmd/upas/spf/spf.c @@ -0,0 +1,800 @@ +#include "spf.h" + +#define vprint(...) if(vflag) fprint(2, __VA_ARGS__) + +enum{ + Traw, + Tip4, + Tip6, + Texists, + Tall, + Tbegin, + Tend, +}; + +char *typetab[] = { + "raw", + "ip4", + "ip6", + "exists", + "all", + "begin", + "end", +}; + +typedef struct Squery Squery; +struct Squery{ + char ver; + char sabort; + char mod; + char *cidrtail; + char *ptrmatch; + char *ip; + char *domain; + char *sender; + char *hello; +}; + +typedef struct Spf Spf; +struct Spf{ + char mod; + char type; + char s[100]; +}; +#pragma varargck type "§" Spf* + +char *txt; +char *netroot = "/net"; +char dflag; +char eflag; +char mflag; +char pflag; +char rflag; +char vflag; + +char *vtab[] = {0, "v=spf1", "spf2.0/"}; + +char* +isvn(Squery *q, char *s, int i) +{ + char *p, *t; + + t = vtab[i]; + if(cistrncmp(s, t, strlen(t))) + return 0; + p = s + strlen(t); + if(i == 2){ + p = strchr(p, ' '); + if(p == nil) + return 0; + } + if(*p && *p++ != ' ') + return 0; + q->ver = i; + return p; +} + +char* +pickspf(Squery *s, char *v1, char *v2) +{ + switch(s->ver){ + default: + case 0: + if(v1) + return v1; + return v2; + case 1: + if(v1) + return v1; + return 0; + case 2: + if(v2) + return v2; + return v1; /* spf2.0/pra,mfrom */ + } +} + +char *ftab[] = {"txt", "spf"}; /* p. 9 */ + +char* +spffetch(Squery *s, char *d) +{ + char *p, *v1, *v2; + int i; + Ndbtuple *t, *n; + + if(txt){ + p = strdup(txt); + txt = 0; + return p; + } + v1 = v2 = 0; + for(i = 0; i < nelem(ftab); i++){ + t = vdnsquery(d, ftab[i], 0); + for(n = t; n; n = n->entry){ + if(strcmp(n->attr, ftab[i])) + continue; + v1 = isvn(s, n->val, 1); + v2 = isvn(s, n->val, 2); + } + if(p = pickspf(s, v1, v2)) + p = strdup(p); + ndbfree(t); + if(p) + return p; + } + return 0; +} + +Spf spftab[200]; +int nspf; +int mod; + +Spf* +spfadd(int type, char *s) +{ + Spf *p; + + if(nspf >= nelem(spftab)) + return 0; + p = spftab+nspf; + p->s[0] = 0; + if(s) + snprint(p->s, sizeof p->s, "%s", s); + p->type = type; + p->mod = mod; + nspf++; + return p; +} + +char *badcidr[] = { + "0.0.0.0/8", + "1.0.0.0/8", + "2.0.0.0/8", + "5.0.0.0/8", + "10.0.0.0/8", + "127.0.0.0/8", + "255.0.0.0/8", + "192.168.0.0/16", + "169.254.0.0/16", + "172.16.0.0/20", + "224.0.0.0/24", /*rfc 3330 says this is /4. not sure */ + "fc00::/7", +}; + +char *okcidr[] = { + "17.0.0.0/8", /* apple. seems dubious. */ +}; + +int +parsecidr(uchar *addr, uchar *mask, char *from) +{ + char *p, buf[50]; + int i, bits, z; + vlong v; + uchar *a; + + strecpy(buf, buf+sizeof buf, from); + if(p = strchr(buf, '/')) + *p = 0; + v = parseip(addr, buf); + if(v == -1) + return -1; + switch((ulong)v){ + default: + bits = 32; + z = 96; + break; + case 6: + bits = 128; + z = 0; + break; + } + + if(p){ + i = strtoul(p+1, &p, 0); + if(i > bits) + i = bits; + i += z; + memset(mask, 0, 128/8); + for(a = mask; i >= 8; i -= 8) + *a++ = 0xff; + if(i > 0) + *a = ~((1<<(8-i))-1); + }else + memset(mask, 0xff, IPaddrlen); + return 0; +} + +/* + * match x.y.z.w to x1.y1.z1.w1/m + */ +int +cidrmatch(char *x, char *y) +{ + uchar a[IPaddrlen], b[IPaddrlen], m[IPaddrlen]; + + if(parseip(a, x) == -1) + return 0; + parsecidr(b, m, y); + maskip(a, m, a); + maskip(b, m, b); + if(!memcmp(a, b, IPaddrlen)) + return 1; + return 0; +} + +int +cidrmatchtab(char *addr, char **tab, int ntab) +{ + int i; + + for(i = 0; i < ntab; i++) + if(cidrmatch(addr, tab[i])) + return 1; + return 0; +} + +int +okcidrlen(char *cidr, int i) +{ + if(i >= 14 && i <= 128) + return 1; + if(cidrmatchtab(cidr, okcidr, nelem(okcidr))) + return 1; + return 0; +} + +int +cidrokay0(char *cidr) +{ + char *p, buf[40]; + uchar addr[IPaddrlen]; + int l, i; + + p = strchr(cidr, '/'); + if(p) + l = p-cidr; + else + l = strlen(cidr); + if(l > 39) + return 0; + if(p){ + i = atoi(p+1); + if(!okcidrlen(cidr, i)) + return 0; + } + memcpy(buf, cidr, l); + buf[l] = 0; + if(parseip(addr, buf) == -1) + return 0; + if(cidrmatchtab(cidr, badcidr, nelem(badcidr))) + return 0; + return 1; +} + +int +cidrokay(char *cidr) +{ + if(!cidrokay0(cidr)){ + fprint(2, "spf: naughty cidr %s\n", cidr); + return 0; + } + return 1; +} + +int +ptrmatch(Squery *q, char *s) +{ + if(!q->ptrmatch || !strcmp(q->ptrmatch, s)) + return 1; + return 0; +} + +Spf* +spfaddcidr(Squery *q, int type, char *s) +{ + char buf[64]; + + if(q->cidrtail){ + snprint(buf, sizeof buf, "%s/%s", s, q->cidrtail); + s = buf; + } + if(cidrokay(s) && ptrmatch(q, s)) + return spfadd(type, s); + return 0; +} + +char* +qpluscidr(Squery *q, char *d, int recur, int *y) +{ + char *p; + + *y = 0; + if(!recur && (p = strchr(d, '/'))){ + q->cidrtail = p + 1; + *p = 0; + *y = 1; + } + return d; +} + +void +cidrtail(Squery *q, char *, int y) +{ + if(!y) + return; + q->cidrtail[-1] = '/'; + q->cidrtail = 0; +} + +void +aquery(Squery *q, char *d, int recur) +{ + int y; + Ndbtuple *t, *n; + + d = qpluscidr(q, d, recur, &y); + t = vdnsquery(d, "any", recur); + for(n = t; n; n = n->entry){ + if(!strcmp(n->attr, "ip")) + spfaddcidr(q, Tip4, n->val); + else if(!strcmp(n->attr, "ipv6")) + spfaddcidr(q, Tip6, n->val); + else if(!strcmp(n->attr, "cname")) + aquery(q, d, recur+1); + } + cidrtail(q, d, y); + ndbfree(t); +} + +void +mxquery(Squery *q, char *d, int recur) +{ + int i, y; + Ndbtuple *t, *n; + + d = qpluscidr(q, d, recur, &y); + i = 0; + t = vdnsquery(d, "mx", recur); + for(n = t; n; n = n->entry) + if(i++ < 10 && !strcmp(n->attr, "mx")) + aquery(q, n->val, recur+1); + ndbfree(t); + cidrtail(q, d, y); +} + +void +ptrquery(Squery *q, char *d, int recur) +{ + char *s, buf[64]; + int i, y; + Ndbtuple *t, *n; + + if(!q->ip){ + fprint(2, "spf: ptr query; no ip\n"); + return; + } + d = qpluscidr(q, d, recur, &y); + i = 0; + dnreverse(buf, sizeof buf, s = strdup(q->ip)); + t = vdnsquery(buf, "ptr", recur); + for(n = t; n; n = n->entry){ + if(!strcmp(n->attr, "dom") || !strcmp(n->attr, "cname")) + if(i++ < 10 && dncontains(d, n->val)){ + q->ptrmatch = q->ip; + aquery(q, n->val, recur+1); + q->ptrmatch = 0; + } + } + ndbfree(t); + free(s); + cidrtail(q, d, y); +} + +/* + * this looks very wrong; see §5.7 which says only a records match. + */ +void +exists(Squery*, char *d, int recur) +{ + Ndbtuple *t; + + if(t = vdnsquery(d, "ip", recur)) + spfadd(Texists, "1"); + else + spfadd(Texists, 0); + ndbfree(t); +} + +void +addfail(void) +{ + mod = '-'; + spfadd(Tall, 0); +} + +void +addend(char *s) +{ + spfadd(Tend, s); + spftab[nspf-1].mod = 0; +} + +Spf* +includeloop(char *s1, int n) +{ + char *s, *p; + int i; + + for(i = 0; i < n; i++){ + s = spftab[i].s; + if(s) + if(p = strstr(s, " -> ")) + if(!strcmp(p+4, s1)) + return spftab+i; + } + return nil; +} + +void +addbegin(int c, char *s0, char *s1) +{ + char buf[0xff]; + + snprint(buf, sizeof buf, "%s -> %s", s0, s1); + spfadd(Tbegin, buf); + spftab[nspf-1].mod = c; +} + +void +ditch(void) +{ + if(nspf > 0) + nspf--; +} + +static void +lower(char *s) +{ + int c; + + for(; c = *s; s++) + if(c >= 'A' && c <= 'Z') + *s = c + 0x20; +} + +int +spfquery(Squery *x, char *d, int include) +{ + char *s, **t, *r, *p, *q, buf[10]; + int i, n, c; + Spf *inc; + + if(include) + if(inc = includeloop(d, nspf-1)){ + fprint(2, "spf: include loop: %s (%s)\n", d, inc->s); + return -1; + } + s = spffetch(x, d); + if(!s) + return -1; + t = malloc(500*sizeof *t); + n = getfields(s, t, 500, 1, " "); + x->sabort = 0; + for(i = 0; i < n && !x->sabort; i++){ + if(!strncmp(t[i], "v=", 2)) + continue; + c = *t[i]; + r = t[i]+1; + switch(c){ + default: + mod = '+'; + r--; + break; + case '-': + case '~': + case '+': + case '?': + mod = c; + break; + } + if(!strcmp(r, "all")){ + spfadd(Tall, 0); + continue; + } + strecpy(buf, buf+sizeof buf, r); + p = strchr(buf, ':'); + if(p == 0) + p = strchr(buf, '='); + q = d; + if(p){ + *p = 0; + q = p+1; + q = r+(q-buf); + } + if(!mflag) + q = macro(q, x->sender, x->domain, x->hello, x->ip); + else + q = strdup(q); + lower(buf); + if(!strcmp(buf, "ip4")) + spfaddcidr(x, Tip4, q); + else if(!strcmp(buf, "ip6")) + spfaddcidr(x, Tip6, q); + else if(!strcmp(buf, "a")) + aquery(x, q, 0); + else if(!strcmp(buf, "mx")) + mxquery(x, d, 0); + else if(!strcmp(buf, "ptr")) + ptrquery(x, d, 0); + else if(!strcmp(buf, "exists")) + exists(x, q, 0); + else if(!strcmp(buf, "include") || !strcmp(buf, "redirect")){ + if(q && *q){ + if(rflag) + fprint(2, "I> %s\n", q); + addbegin(mod, r, q); + if(spfquery(x, q, 1) == -1){ + ditch(); + addfail(); + }else + addend(r); + } + } + free(q); + } + free(t); + free(s); + return 0; +} + +char* +url(char *s) +{ + char buf[64], *p, *e; + int c; + + p = buf; + e = p + sizeof buf; + *p = 0; + while(c = *s++){ + if(c >= 'A' && c <= 'Z') + c += 0x20; + if(c <= ' ' || c == '%' || c & 0x80) + p = seprint(p, e, "%%%2.2X", c); + else + p = seprint(p, e, "%c", c); + } + return strdup(buf); +} + +void +spfinit(Squery *q, char *dom, int argc, char **argv) +{ + uchar a[IPaddrlen]; + + memset(q, 0, sizeof q); + q->ip = argc>0? argv[1]: 0; + if(q->ip && parseip(a, q->ip) == -1) + sysfatal("bogus ip"); + q->domain = url(dom); + q->sender = argc>2? url(argv[2]): 0; + q->hello = argc>3? url(argv[3]): 0; + mod = 0; /* BOTCH */ +} + +int +§fmt(Fmt *f) +{ + char *p, *e, buf[115]; + Spf *spf; + + spf = va_arg(f->args, Spf*); + if(!spf) + return fmtstrcpy(f, "<nil>"); + e = buf+sizeof buf; + p = buf; + if(spf->mod && spf->mod != '+') + *p++ = spf->mod; + p = seprint(p, e, "%s", typetab[spf->type]); + if(spf->s[0]) + seprint(p, e, " : %s", spf->s); + return fmtstrcpy(f, buf); +} + +static Spf head; + +struct{ + int i; +}walk; + +int +invertmod(int c) +{ + switch(c){ + case '?': + return '?'; + case '+': + return '-'; + case '-': + return '+'; + case '~': + return '?'; + } + return 0; +} + +#define reprint(...) if(vflag && recur == 0) fprint(2, __VA_ARGS__) + +int +spfwalk(int all, int recur, char *ip) +{ + int match, bias, mod, r; + Spf *s; + + r = 0; + bias = 0; + if(recur == 0) + walk.i = 0; + for(; walk.i < nspf; walk.i++){ + s = spftab+walk.i; + mod = s->mod; + switch(s->type){ + default: + abort(); + case Tbegin: + walk.i++; + match = spfwalk(s->s[0] == 'r', recur+1, ip); + if(match < 0) + mod = invertmod(mod); + break; + case Tend: + return r; + case Tall: + match = 1; + break; + case Texists: + match = s->s[0]; + break; + case Tip4: + case Tip6: + match = cidrmatch(ip, s->s); + break; + } + if(!r && match) + switch(mod){ + case '~': + reprint("bias %§\n", s); + bias = '~'; + case '?': + break; + case '-': + if(all || s->type !=Tall){ + vprint("fail %§\n", s); + r = -1; + } + break; + case '+': + default: + vprint("match %§\n", s); + r = 1; + } + } + /* recur == 0 */ + if(r == 0 && bias == '~') + r = -1; + return r; +} + +/* ad hoc and noncomprehensive */ +char *tccld[] = {"au", "ca", "gt", "id", "pk", "uk", "ve", }; +int +is3cctld(char *s) +{ + int i; + + if(strlen(s) != 2) + return 0; + for(i = 0; i < nelem(tccld); i++) + if(!strcmp(tccld[i], s)) + return 1; + return 0; +} + +char* +rootify(char *d) +{ + char *p, *q; + + if(!(p = strchr(d, '.'))) + return 0; + p++; + if(!(q = strchr(p, '.'))) + return 0; + q++; + if(!strchr(q, '.') && is3cctld(q)) + return 0; + return p; +} + +void +usage(void) +{ + fprint(2, "spf [-demrpv] [-n netroot] dom [ip sender helo]\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *s, *d, *e; + int i, j, t[] = {0, 3}; + Squery q; + + ARGBEGIN{ + case 'd': + dflag = 1; + break; + case 'e': + eflag = 1; + break; + case 'm': + mflag = 1; + break; + case 'n': + netroot = EARGF(usage()); + break; + case 'p': + pflag = 1; + break; + case 'r': + rflag = 1; + break; + case 't': + txt = EARGF(usage()); + break; + case 'v': + vflag = 1; + break; + default: + usage(); + }ARGEND + + if(argc < 1 || argc > 4) + usage(); + if(argc == 1) + pflag = 1; + fmtinstall(L'§', §fmt); + fmtinstall('I', eipfmt); + fmtinstall('M', eipfmt); + + e = "none"; + for(i = 0; i < nelem(t); i++){ + if(argc <= t[i]) + break; + d = argv[t[i]]; + for(j = 0; j < i; j++) + if(!strcmp(argv[t[j]], d)) + goto loop; + for(s = d; ; s = rootify(s)){ + if(!s) + goto loop; + spfinit(&q, d, argc, argv); /* or s? */ + addbegin('+', ".", s); + if(spfquery(&q, s, 0) != -1) + break; + } + if(eflag && nspf) + addfail(); + e = ""; + if(pflag) + for(j = 0; j < nspf; j++) + print("%§\n", spftab+j); + if(argc >= t[i] && argc > 1) + if(spfwalk(1, 0, argv[1]) == -1) + exits("fail"); +loop:; + } + exits(e); +} diff --git a/sys/src/cmd/upas/spf/spf.h b/sys/src/cmd/upas/spf/spf.h new file mode 100644 index 000000000..b37bad864 --- /dev/null +++ b/sys/src/cmd/upas/spf/spf.h @@ -0,0 +1,12 @@ +/* © 2008 erik quanstrom; plan 9 license */ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ndb.h> +#include <ip.h> + +char *macro(char*, char*, char*, char*, char*); + +Ndbtuple* vdnsquery(char*, char*, int); +int dncontains(char*, char *); +void dnreverse(char*, int, char*); diff --git a/sys/src/cmd/upas/spf/testsuite b/sys/src/cmd/upas/spf/testsuite new file mode 100644 index 000000000..1b55f6c2a --- /dev/null +++ b/sys/src/cmd/upas/spf/testsuite @@ -0,0 +1,9 @@ +#!/bin/rc +for(i in '%{s}' '%{o}' '%{d}' '%{d4}' '%{d3}' '%{d2}' '%{d1}' '%{dr}' '%{d2r}' '%{l}' '%{l-}' '%{lr}' '%{lr-}' '%{l1r-}') + mtest $i 'strong-bad@email.example.com' email.example.com helounknown 192.0.2.3 +for(i in '%{i}') + mtest $i 'strong-bad@email.example.com' email.example.com helounknown 2001:db8::cb01 +for(i in '%{ir}.%{v}._spf.%{d2}' '%{lr-}.lp._spf.%{d2}' '%{lr-}.lp.%{ir}.%{v}._spf.%{d2}' '%{ir}.%{v}.%{lr-}.lp._spf.%{d2}') + mtest $i 'strong-bad@email.example.com' email.example.com helounknown 2001:db8::cb01 +for(i in '%{ir}.%{v}._spf.%{d2}' '%{lr-}.lp._spf.%{d2}' '%{lr-}.lp.%{ir}.%{v}._spf.%{d2}' '%{ir}.%{v}.%{lr-}.lp._spf.%{d2}') + mtest $i 'strong-bad@email.example.com' email.example.com helounknown 192.0.2.3 |