#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, "<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 '?'; /* '~'? 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);
}
|