#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 *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)) != 0) return 0; p = s + strlen(t); if(i == 2) p = strchr(p, ' '); 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) { 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); }