Plan 9 from Bell Labs’s /usr/web/sources/patch/sorry/spf/spf.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#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);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.