new spf application to go with validatesender & smtpd submisssions. Notes: Tue Nov 18 16:02:45 EST 2008 geoff subsumed by nupas. Reference: /n/sources/patch/sorry/spf Date: Tue Apr 22 19:10:31 CES 2008 Signed-off-by: quanstro@quanstro.net Reviewed-by: geoff --- /sys/src/cmd/upas/spf/dns.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/upas/spf/dns.c Sun Jun 1 00:45:07 2008 @@ -0,0 +1,78 @@ +#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) +{ + for (; ; s++) + if(strcmp(d, s) == 0) + return 1; + else if((s = strchr(s, '.')) == nil) + return 0; +} --- /sys/src/cmd/upas/spf/macro.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/upas/spf/macro.c Sun Jun 1 00:53:41 2008 @@ -0,0 +1,305 @@ +#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") == 0 || strcmp(n->attr, "ipv6") == 0){ + if(strcmp(n->val, match) == 0){ + r = 1; + break; + } + }else if(strcmp(n->attr, "cname") == 0) + 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) +{ + int nlist, i; + char *s, buf[64], *a, *list[11]; + 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") == 0 || + strcmp(n->attr, "cname") == 0) && + 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) == 0){ + 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) +{ + int i, n; + char *p, *e, buf[100], *f[32], sep[2]; + + 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) +{ + int i, n; + char *p, *e, buf[100], *f[32], sep[2]; + + 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) +{ + int i, c; + char *p; + 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; + } + /* fallthrough */ + default: + return 0; + } + } + mputc(&m, 0); + return strdup(m.buf); +} --- /sys/src/cmd/upas/spf/mkfile Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/upas/spf/mkfile Tue Apr 22 19:07:57 2008 @@ -0,0 +1,14 @@ +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) +{ + int i; + char *p, *v1, *v2; + 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]) != 0) + 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", +}; + +int +parsecidr(uchar *addr, uchar *mask, char *from) +{ + int i, bits, z; + vlong v; + char *p, buf[50]; + uchar *a; + + strecpy(buf, buf+sizeof buf, from); + if((p = strchr(buf, '/')) != nil) + *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) == 0) + 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 +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 >= sizeof buf) + return 0; + if(p){ + i = atoi(p+1); + if(i < 14 || i > 128) + 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, "naughty cidr %s\n", cidr); + return 0; + } + return 1; +} + +int +ptrmatch(Squery *q, char *s) +{ + return !q->ptrmatch || strcmp(q->ptrmatch, s) == 0; +} + +Spf* +spfaddcidr(Squery *q, int type, char *s) +{ + if(cidrokay(s) && ptrmatch(q, s)) + return spfadd(type, s); + return 0; +} + +void +aquery(Squery *q, char *d, int recur) +{ + Ndbtuple *t, *n; + + t = vdnsquery(d, "any", recur); + for(n = t; n; n = n->entry){ + if(strcmp(n->attr, "ip") == 0) + spfaddcidr(q, Tip4, n->val); + else if(strcmp(n->attr, "ipv6") == 0) + spfaddcidr(q, Tip6, n->val); + else if(strcmp(n->attr, "cname") == 0) + aquery(q, d, recur+1); + } + ndbfree(t); +} + +void +mxquery(Squery *q, char *d, int recur) +{ + int i; + Ndbtuple *t, *n; + + i = 0; + t = vdnsquery(d, "mx", recur); + for(n = t; n; n = n->entry) + if(i++ < 10 && strcmp(n->attr, "mx") == 0) + aquery(q, n->val, recur+1); + ndbfree(t); +} + +void +ptrquery(Squery *q, char *d, int recur) +{ + int i; + char *s, buf[64]; + Ndbtuple *t, *n; + + if(!q->ip){ + fprint(2, "ptr query; no ip\n"); + return; + } + 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") == 0 || strcmp(n->attr, "cname") == 0) && + i++ < 10 && dncontains(d, n->val)){ + q->ptrmatch = q->ip; + aquery(q, n->val, recur+1); + q->ptrmatch = 0; + } + } + ndbfree(t); + free(s); +} + +/* + * 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, "a", 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; +} + +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) != nil; s++) + if(isascii(c) && isupper(c)) + *s = tolower(c); +} + +int +spfquery(Squery *x, char *d) +{ + int i, n, c; + char *s, **t, *r, *p, *q, buf[10]; + + 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) == 0) + 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") == 0){ + 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") == 0) + spfaddcidr(x, Tip4, q); + else if(strcmp(buf, "ip6") == 0) + spfaddcidr(x, Tip6, q); + else if(strcmp(buf, "a") == 0) + aquery(x, q, 0); + else if(strcmp(buf, "mx") == 0) + mxquery(x, d, 0); + else if(strcmp(buf, "ptr") == 0) + ptrquery(x, d, 0); + else if(strcmp(buf, "exists") == 0) + exists(x, q, 0); + else if(strcmp(buf, "include") == 0 || + strcmp(buf, "redirect") == 0) + if(q && *q){ + if(rflag) + fprint(2, "I> %s\n", q); + addbegin(mod, r, q); + if(spfquery(x, q) == -1){ + ditch(); + addfail(); + }else + addend(r); + } + free(q); + } + free(t); + free(s); + return 0; +} + +char* +url(char *s) +{ + int c; + char buf[64], *p, *e; + + p = buf; + e = p + sizeof buf; + *p = 0; + while(c = *s++){ + if(isascii(c) && isupper(c)) + c = tolower(c); + if(isascii(c) && iscntrl(c) || c == '%' || !isascii(c)) + 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, ""); + 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 '?'; /* '~'? TODO */ + 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 = '~'; + break; + case '?': + break; + case '-': + if(all || s->type != Tall){ + vprint("fail %§\n", s); + r = -1; + } + break; + case '+': + default: + vprint("match %§\n", s); + r = 1; + break; + } + } + /* 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) == 0) + return 1; + return 0; +} + +char* +rootify(char *d) +{ + char *p, *q; + + if((p = strchr(d, '.')) == nil) + return 0; + p++; + if((q = strchr(p, '.')) == nil) + return 0; + q++; + if(strchr(q, '.') == nil && is3cctld(q)) + return 0; + return p; +} + +void +usage(void) +{ + fprint(2, "spf [-demprv] [-n netroot] [-t text] dom [ip sender helo]\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int i, j, t[] = { 0, 3 }; + char *s, *d, *e; + 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) == 0) + goto loop; + for(s = d; ; s = rootify(s)){ + if(!s) + goto loop; + spfinit(&q, d, argc, argv); /* or s? */ + if(spfquery(&q, s) != -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 && spfwalk(1, 0, argv[1]) == -1) + exits("fail"); +loop: + ; + } + exits(e); +} --- /sys/src/cmd/upas/spf/spf.h Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/upas/spf/spf.h Sun Jun 1 04:36:17 2008 @@ -0,0 +1,13 @@ +/* © 2008 erik quanstrom; plan 9 license */ +#include +#include +#include +#include +#include +#include + +char *macro(char*, char*, char*, char*, char*); + +Ndbtuple* vdnsquery(char*, char*, int); +int dncontains(char*, char *); +void dnreverse(char*, int, char*); --- /sys/src/cmd/upas/spf/testsuite Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/upas/spf/testsuite Tue Apr 22 19:08:25 2008 @@ -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 --- /sys/man/8/spf Thu Jan 1 00:00:00 1970 +++ /sys/man/8/spf Sun Jun 1 04:49:22 2008 @@ -0,0 +1,128 @@ +.TH SPF 8 +.SH NAME +spf \- evaluate Sender Policy Framework records +.SH SYNOPSIS +.B upas/spf +[ +.B -demprv +] +[ +.B -n +.I netroot +] [ +.B -t +text +] +.I domain +[ +.I ip +.I from +.I hello +] +.SH DESCRIPTION +.I Spf +parses Sender Policy Framework (SPF) records for +.I domain +and validates them against any additional arguments. +If there are no +additional arguments, the internal representation of the SPF records +is printed. +Typically, +.I spf +is invoked by +.IR smtpd (8) +through the +.B /mail/lib/validatesender +script. +There are four possible results of an +.I spf +invocation: +success, no match, no record found and timeout. +Unsuccessful results +are indicated by exit codes beginning with +.BR fail , +.BR none , +and +.BR deferred , +respectively. +.PP +The policy implemented is that +negatively-biased results are the equivalent +of a negative match. +Thus +.B ~all +is treated the same as +.BR -all . +The +.B -e +option makes this policy even more draconian, escalating neutral results +to failure. +This is useful for sites like +.B gmail.com +which enumerate all allowed hosts but end with an inclusive +neutral result. +This option is not recomended as a default for all sites. +.PP +Options are: +.TF netroot +.TP +.B -d +print DNS queries as performed +.TP +.B -e +escalate; treat +.B ?all +as +.BR -all . +The result +.B ~all +is always treated the same as +.BR -all . +.TP +.B -m +ignore macros +.TP +.BI -n " netroot" +use the IP stack rooted at +.BI netroot . +.TP +.B -p +print the internal representation of the SPF records. +This is the default if only one argument is given. +.TP +.B -r +trace +.B include +and +.B redirect +elements. +.TP +.B -v +print records resulting in positive +or negative match or bias. +.SH "SEE ALSO" +.IR ndb (8), +.IR smtp (8) +.br +.PD 0 +.TF /lib/rfc/rfc4408 +.TP +.B /lib/rfc/rfc4408 +Sender Policy Framework +.TP +.B /lib/rfc/rfc4406 +SenderID +.SH SOURCE +.B /sys/src/cmd/upas/spf +.SH BUGS +.L exp +records are ignored. +.PP +Improperly-placed +.B redirect +queries are not evaluated last. +.PP +.I Spf +uses higher DNS query limits than allowed by the RFC, +as the authors of the RFC use SPF records that require +twice the allowed number of queries.