diff options
Diffstat (limited to 'rfc1035/spf.c')
| -rw-r--r-- | rfc1035/spf.c | 1466 | 
1 files changed, 1466 insertions, 0 deletions
| diff --git a/rfc1035/spf.c b/rfc1035/spf.c new file mode 100644 index 0000000..2f0de3c --- /dev/null +++ b/rfc1035/spf.c @@ -0,0 +1,1466 @@ +/* +** Copyright 2004-2011 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#include	"config.h" +#include	"spf.h" +#include	"rfc1035mxlist.h" +#include	<stdio.h> +#include	<ctype.h> +#include	<stdlib.h> +#include	<string.h> +#include	<errno.h> +#include	<sys/types.h> +#if TIME_WITH_SYS_TIME +#include	<sys/time.h> +#include	<time.h> +#else +#if HAVE_SYS_TIME_H +#include	<sys/time.h> +#else +#include	<time.h> +#endif +#endif + +struct rfc1035_spf_info { +	const char *mailfrom; +	const char *current_domain; +	const char *tcpremoteip; +	const char *tcpremotehost; +	const char *helodomain; +	const char *mydomain; +	char *errmsg_buf; +	size_t errmsg_buf_size; + +	size_t *lookup_cnt; + +	struct rfc1035_res res; +}; + +static void set_err_msg(char *errmsg_buf, +			size_t errmsg_buf_size, +			const char *errmsg) +{ +	size_t l=strlen(errmsg); + +	if (errmsg_buf_size == 0) +		return; + +	--errmsg_buf_size; + +	if (l >= errmsg_buf_size) +		l=errmsg_buf_size; +	memcpy(errmsg_buf, errmsg, l); +	errmsg_buf[l]=0; +} + +static char lookup(struct rfc1035_spf_info *info); + +char rfc1035_spf_lookup(const char *mailfrom, +			const char *tcpremoteip, +			const char *tcpremotehost, +			const char *helodomain, +			const char *mydomain, +			char *errmsg_buf, +			size_t errmsg_buf_size) +{ +	size_t lookup_cnt=0; +	struct rfc1035_spf_info info; +	char result; + +	if (!tcpremoteip) tcpremoteip=""; +	if (!tcpremotehost) tcpremotehost=""; +	if (!helodomain) helodomain=""; +	if (!mydomain) mydomain=""; + +	if (errmsg_buf && errmsg_buf_size) +		*errmsg_buf=0; + +	/* +	** If the <responsible-sender> has no localpart, clients MUST +	** substitute the string "postmaster" for the localpart. +	*/ +	if (strchr(mailfrom, '@') == NULL) +	{ +		char *buf=malloc(sizeof("postmaster@")+strlen(mailfrom)); +		char err_code; + +		if (buf == NULL) +		{ +			set_err_msg(errmsg_buf, errmsg_buf_size, +				    strerror(errno)); +			return SPF_ERROR; +		} + +		err_code=rfc1035_spf_lookup(strcat(strcpy(buf, "postmaster@"), +						   mailfrom), +					    tcpremoteip, +					    tcpremotehost, +					    helodomain, +					    mydomain, +					    errmsg_buf, +					    errmsg_buf_size); +		free(buf); +		return err_code; +	} + +	memset(&info, 0, sizeof(info)); + +	info.mailfrom=mailfrom; + +	/* +	** The <current-domain> is initially drawn from the +	** <responsible-sender>.  Recursive mechanisms such as +	** Include and Redirect replace the initial +	** <current-domain> with another domain.  However, they +	** do not change the value of the <responsible-sender>. +	*/ +	info.current_domain=strrchr(mailfrom, '@')+1; + +	info.tcpremoteip=tcpremoteip; +	info.tcpremotehost=tcpremotehost; +	info.errmsg_buf=errmsg_buf; +	info.errmsg_buf_size=errmsg_buf_size; +	info.helodomain=helodomain; +	info.mydomain=mydomain; +	info.lookup_cnt=&lookup_cnt; + +	rfc1035_init_resolv(&info.res); + +	result=lookup(&info); + +	if (errmsg_buf[0] == 0) +	{ +		static const char errmsg[]="Address %s the Sender Policy Framework"; +		char *p=malloc(sizeof(errmsg)+strlen(mailfrom)+20); + +		if (p) +			sprintf(p, errmsg, result == SPF_PASS +				? "passes":"does not pass"); + +		set_err_msg(errmsg_buf, errmsg_buf_size, +			    p ? p:strerror(errno)); +		if (p) free(p); +	} +	rfc1035_destroy_resolv(&info.res); +	return result; +} + +static int isspf1(struct rfc1035_reply *reply, int n) +{ +	char	txtbuf[256]; +	const char *p; + +	rfc1035_rr_gettxt(reply->allrrs[n], 0, txtbuf); + +	for (p=txtbuf; *p; p++) +		if (!isspace((int)(unsigned char)*p)) +			break; + +	if (strncasecmp(p, "v=spf1", 6) == 0 && +	    (p[6] == 0 || +	     isspace((int)(unsigned char)p[6]))) +		return 1; + +	return 0; +} + +char rfc1035_spf_gettxt_res(const char *current_domain, +			    char *buf, +			    struct rfc1035_res *res) +{ +	struct	rfc1035_reply *reply; +	char	namebuf[RFC1035_MAXNAMESIZE+1]; +	int n, o; + + +	namebuf[0]=0; +	strncat(namebuf, current_domain, RFC1035_MAXNAMESIZE); + +	if (rfc1035_resolve_cname(res, namebuf, +				  RFC1035_TYPE_TXT, RFC1035_CLASS_IN, +				  &reply, 0) < 0 || +	    reply == 0 || +	    (n=rfc1035_replysearch_an(res, reply, namebuf, RFC1035_TYPE_TXT, +				      RFC1035_CLASS_IN, 0)) < 0) +	{ +		switch (reply ? reply->rcode: +			RFC1035_RCODE_SERVFAIL) { +		case RFC1035_RCODE_NOERROR: +			rfc1035_replyfree(reply); +			return SPF_NONE; +		case RFC1035_RCODE_NXDOMAIN: +			rfc1035_replyfree(reply); +			return SPF_UNKNOWN; +		default: +			break; +		} +		if (reply) +			rfc1035_replyfree(reply); +		return SPF_ERROR; +	} + +	while (n >= 0) +	{ +		if (isspf1(reply, n)) +			break; + +		n=rfc1035_replysearch_an(res, reply, namebuf, +					 RFC1035_TYPE_TXT, RFC1035_CLASS_IN, +					 n+1); +	} + +	if (n >= 0) +	{ +		for (o=n; (o=rfc1035_replysearch_an(res, reply, namebuf, +						    RFC1035_TYPE_TXT, +						    RFC1035_CLASS_IN, +						    o+1)) >= 0; ) +		{ + +			/* +			** +			** A domain MUST NOT return multiple records that +			** begin with the version "v=spf1".  If more than +			** one "v=spf1" record is returned, this constitutes +			** a syntax error and the result is "unknown". +			*/ + +			if (isspf1(reply, o)) +			{ +				rfc1035_replyfree(reply); +				return SPF_UNKNOWN; +			} +		} + +		rfc1035_rr_gettxt(reply->allrrs[n], 0, buf); +		rfc1035_replyfree(reply); +		return SPF_PASS; +	} +	rfc1035_replyfree(reply); +	return SPF_UNKNOWN; +} + +char rfc1035_spf_gettxt(const char *current_domain, +			char *buf) +{ +	struct rfc1035_res res; +	char c; + +	rfc1035_init_resolv(&res); + +	c=rfc1035_spf_gettxt_res(current_domain, buf, &res); + +	rfc1035_destroy_resolv(&res); + +	return c; +} + +/* +** Chop up an SPF record into whitespace-delimited words. +** get_words() is called twice: once with wordptr=NULL - return # of words, +** second time with wordptr!=NULL, parse the words. +*/ + +static unsigned get_words(char *record, char **wordptr) +{ +	unsigned n=0; + +	while (*record) +	{ +		if (isspace((int)(unsigned char)*record)) +		{ +			++record; +			continue; +		} + +		if (wordptr) +			*wordptr++=record; +		++n; + +		while (*record) +		{ +			if (isspace((int)(unsigned char)*record)) +				break; +			++record; +		} + +		if (*record && wordptr) +			*record++=0; +	} +	return n; +} + +static char spf_compute(char **words, +			struct rfc1035_spf_info *info); + +char rfc1035_spf_compute(char *record, +			 struct rfc1035_spf_info *info) +{ +	unsigned n=get_words(record, NULL); +	char **words=malloc((n+1)*sizeof(char *)); +	char rc; + +	if (words == NULL) +	{ +		set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +			    strerror(errno)); +		return SPF_ERROR; +	} + +	get_words(record, words); +	words[n]=0; + +	rc=spf_compute(words, info); +	free(words); +	return rc; +} + +static char mechanism(const char *name, +		      struct rfc1035_spf_info *info); + +static void setexp(const char *name, +		   struct rfc1035_spf_info *info); +static char *expand(const char *str, +		    struct rfc1035_spf_info *info); + + +static char spf_compute(char **words, +			struct rfc1035_spf_info *info) +{ +	size_t i; +	char rc; +	const char *exp=NULL; +	const char *redirect=NULL; + +	for (i=0; words[i]; i++) +		if (strncasecmp(words[i], "exp=", 4) == 0) +			exp=words[i]+4; + +	for (i=0; words[i]; i++) +	{ +		const char *name; +		char prefix; + +		if (strncasecmp(words[i], "redirect=", 9) == 0) +			redirect=words[i]+9; + +		if (strchr(words[i], '=')) +			continue; + +		name=words[i]; + +		switch (*name) { +		case '+': +		case '-': +		case '?': +		case '~': +			prefix=*name++; +			break; +		default: +			prefix='+'; +			break; +		} + +		rc=mechanism(name, info); + +		/* +		** When a mechanism is evaluated, one of three things can +		** happen: it can match, it can not match, or it can throw an +		** exception. +		*/ + +		if (rc == SPF_PASS) +		{ +			/* +			** If it matches, processing ends and the prefix value +			** is returned as the result of that record. +			*/ + +			if (prefix != SPF_PASS && exp) +				setexp(exp, info); +			return prefix; +		} + +		if (rc == SPF_FAIL) +		{ +			/* +			** If it does not match, processing continues with +			** the next +			** mechanism. +			*/ + +			continue; +		} + +		/* +		** If it throws an exception, mechanism processing ends and +		** the exception value is returned (either "error" +		** indicating a temporary failure, usually DNS-related, or +		** "unknown" indicating a syntax error or other permanent +		** failure resulting in incomplete processing.) +		*/ +		return rc; +	} + +	if (redirect) +	{ +		/* +		** If all mechanisms fail to match, and a redirect modifier +		** is present, then processing proceeds as follows. +		** +		** The domain-spec portion of the redirect section is expanded +		** as per the macro rules in section 7.  The resulting string +		** is a new domain that is now queried:  The <current-domain> +		** is set to this new domain, and the new domain's SPF record +		** is fetched and processed.  Note that <responsible-sender> +		** does not change. +		** +		** The result of this new query is then considered the result +		** of original query. +		*/ + + +		char *new_domain; +		struct rfc1035_spf_info newinfo; +		char rc; + +		new_domain=expand(redirect, info); + +		if (!new_domain) +			return SPF_ERROR; + +		newinfo= *info; +		newinfo.current_domain=new_domain; + +		rc=lookup(&newinfo); +		free(new_domain); +		return rc; +	} + +	/* +	** If none of the mechanisms match and there is no redirect modifier, +	** then the result of the SPF query is "neutral". +	*/ + +	if (exp) +		setexp(exp, info); + +	return SPF_NEUTRAL; +} + +static int get_dual_cidr_length(const char *p) +{ +#if RFC1035_IPV6 +	const char *q; + +	for (q=p; *q; q++) +		if (*q == '/' && q[1] == '/') +			return atoi(q+2); + +	return atoi(p+1)+12*8; +#else +	if (p[1] == '/') +		return -1; +	return atoi(p+1); +#endif +} + +static int ip_compare(const RFC1035_ADDR *a, +		      const RFC1035_ADDR *b, +		      int pfix) +{ +	const unsigned char *ca, *cb; +	unsigned i; + +	if (pfix < 0) +		return 0; + +	ca=(const unsigned char *)a; +	cb=(const unsigned char *)b; + +	for (i=0; i<sizeof(RFC1035_ADDR); i++) +	{ +		int bits=pfix>8?8:pfix; +		unsigned char m=(unsigned char )(~0 << (8-bits)); + +		if ((ca[i] & m) != (cb[i] & m)) +			return 0; + +		pfix -= bits; +	} +	return 1; +} + +static void get_domain_pfix(struct rfc1035_spf_info *info, +			    const char *start, +			    char **domain_ptr, +			    int  *pfix_ptr) +{ +	*pfix_ptr=sizeof(RFC1035_ADDR)*8; + +	if (*start == 0 || *start == '/') +	{ +		*domain_ptr=strdup(strrchr(info->mailfrom, '@')+1); + +		if (*start == '/') +			*pfix_ptr=get_dual_cidr_length(start); +	} +	else +	{ +		char *p; + +		*domain_ptr=strdup(*start == ':' ? start+1:start); + +		p=strchr(*domain_ptr, '/'); +		if (p) +		{ +			*pfix_ptr=get_dual_cidr_length(p); +			*p++=0; +		} + +		if (*domain_ptr == 0) +		{ +			free(*domain_ptr); +			*domain_ptr=strdup(strrchr(info->mailfrom, +						   '@')+1); +		} +	} + +	if (!*domain_ptr) +		set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +			    strerror(errno)); +} + +struct ptr_info { +	const char *name; +	struct rfc1035_spf_info *info; +	RFC1035_ADDR addr; +	int found; +	int error; +}; + +static void check_ptr(const char *ptr, void *void_arg) +{ +	struct ptr_info *pinfo=(struct ptr_info *)void_arg; +	RFC1035_ADDR *addr; +	unsigned addr_cnt; +	int rc; +	unsigned i; + +	if (pinfo->found) +		return; /* No need */ + +	rc=rfc1035_a(&pinfo->info->res, ptr, &addr, &addr_cnt); + +	if (rc > 0) +		pinfo->error=1; +	if (rc) +		return; + +	for (i=0; i<addr_cnt; i++) +	{ +		if (memcmp(&addr[i], &pinfo->addr, +			   sizeof(pinfo->addr)) == 0) +			break; +	} + +	if (i < addr_cnt) +	{ +		size_t l1, l2; + +		if (strcasecmp(ptr, pinfo->name) == 0) +			pinfo->found=1; + +		l1=strlen(ptr); +		l2=strlen(pinfo->name); + +		if (l2 < l1) +		{ +			ptr=ptr+l1-l2; + +			if (ptr[-1] == '.' && strcasecmp(ptr, pinfo->name)==0) +				pinfo->found=1; +		} +	} +	free(addr); +} + +static char do_ptr(const char *name, +		   struct rfc1035_spf_info *info) +{ +	struct ptr_info pinfo; + +	if (*name++ == ':') +		pinfo.name=name; +	else +		pinfo.name=strrchr(info->mailfrom, '@')+1; + +	pinfo.info=info; +	pinfo.found=0; +	pinfo.error=0; + +	/* +	** First the <sending-host>'s name is looked up using this +	** procedure: +	** +	** perform a PTR lookup against the <sending-host>'s IP.  For +	** each record returned, validate the host name by looking up +	** its IP address.  If the <sending-host>'s IP is among the +	** returned IP addresses, then that host name is validated. +	** +	** Check all validated hostnames to see if they end in the +	** <target-name> domain.  If any do, this mechanism matches. +	** If no validated hostname can be found, or if none of the +	** validated hostnames end in the <target-name>, this +	** mechanism fails to match. +	*/ + +	if (rfc1035_aton(info->tcpremoteip, &pinfo.addr) < 0) +	{ +		set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +			    "Invalid tcpremoteip.\n"); +		return SPF_FAIL; +	} + +	if (rfc1035_ptr_x(&info->res, &pinfo.addr, +			  check_ptr, &pinfo) < 0) +	{ +		if (errno == ENOENT) +			return SPF_FAIL; + +		return SPF_ERROR; +	} + +	if (pinfo.found) +		return SPF_PASS; +	if (pinfo.error) +	{ +		set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +			    "ptr lookup failed.\n"); +		return SPF_UNKNOWN; +	} +	return SPF_FAIL; +} + +static char do_ipcheck(const char *name, struct rfc1035_spf_info *info, +		       int pfix_add) +{ +	char *addrptr; +	char *p; +	int pfix; +	RFC1035_ADDR addr, addrcmp; + +	/* +	** These mechanisms test if the <sending-host> falls into a +	** given IP network. +	** +	** The <sending-host> is compared to the given network.  If +	** they match, the mechanism matches. +	*/ + +	if (rfc1035_aton(info->tcpremoteip, &addr) < 0) +	{ +		set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +			    "Invalid tcpremoteip.\n"); +		return SPF_FAIL; +	} + +	if ((addrptr=strdup(name+4)) == NULL) +	{ +		set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +			    strerror(errno)); +		return SPF_ERROR; +	} + +	p=strrchr(addrptr, '/'); +	pfix=sizeof(RFC1035_ADDR)*8; + +	if (p) +	{ +		*p++=0; +		pfix=atol(p)+pfix_add; +	} + +	if (rfc1035_aton(addrptr, &addrcmp) < 0) +	{ +		free(addrptr); +		return SPF_FAIL; +	} +	free(addrptr); +	if (ip_compare(&addr, &addrcmp, pfix)) +		return SPF_PASS; +	return SPF_FAIL; +} + +static char mechanism(const char *name, +		      struct rfc1035_spf_info *info) +{ +	if (strcasecmp(name, "all") == 0) +	{ +		/* +		** The "all" mechanism is a test that always matches.  It is +		** used as the rightmost mechanism in an SPF record to +		** provide an explicit default. +		*/ +		return SPF_PASS; +	} + +	if (strncasecmp(name, "include:", 8) == 0) +	{ +		char *new_domain; +		struct rfc1035_spf_info newinfo; +		char rc; + +		/* +		** The "include" mechanism triggers a recursive SPF query.  The +		** domain-spec is expanded as per section 7.  Then a new query +		** is launched using the resulting string as the +		** <current-domain>.  The <responsible-sender> stays the same. +		*/ + +		new_domain=expand(name+8, info); + +		if (!new_domain) +			return SPF_ERROR; + +		newinfo= *info; +		newinfo.current_domain=new_domain; + +		/* +		**      included    include +		**      query       mechanism      SPF +		**      result      result         processing +		**      -------- -- -------------- ------------------------------------- +		**      pass     => match,         return the prefix value for "include" +		**      fail     => no match,      continue processing +		**      softfail => no match,      continue processing +		**      neutral  => no match,      continue processing +		**      error    => throw error,   abort processing, return error +		**      unknown  => throw unknown, abort processing, return unknown +		**      none     => throw unknown, abort processing, return unknown +		*/ + +		rc=lookup(&newinfo); +		free(new_domain); + +		switch (rc) { +		case SPF_PASS: +			return SPF_PASS; +		case SPF_FAIL: +		case SPF_SOFTFAIL: +		case SPF_NEUTRAL: +			return SPF_FAIL; +		case SPF_ERROR: +			return SPF_ERROR; +		default: +			return SPF_UNKNOWN; +		} +	} + +	if (strncasecmp(name, "a", 1) == 0 && +	    (name[1] == 0 || name[1] == ':' || name[1] == '/')) +	{ +		char *domain_spec; +		int pfix; +		RFC1035_ADDR addr; + +		RFC1035_ADDR *iaptr; +		unsigned iasize; +		int rc; +		unsigned ii; + +		/* +		** This mechanism matches if the <sending-host> is one of the +		** <target-name>'s IP addresses. +		** +		** A = "a" [ ":" domain-spec ] [ dual-cidr-length ] +		** +		** The <sending-host> is compared to the IP address(es) of the +		** <target-name>.  If any address matches, the mechanism +		** matches. +		*/ + +		get_domain_pfix(info, name+1, &domain_spec, &pfix); + +		if (!domain_spec) +			return SPF_ERROR; + +		if (rfc1035_aton(info->tcpremoteip, &addr) < 0) +		{ +			free(domain_spec); +			set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +				    "Invalid tcpremoteip.\n"); +			return SPF_FAIL; +		} + +		rc=rfc1035_a(&info->res, +			     domain_spec, +			     &iaptr, +			     &iasize); + +		free(domain_spec); + +		if (rc != 0) +		{ +			set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +				    "IP address lookup failed.\n"); +			return SPF_UNKNOWN; +		} + +		for (ii=0; ii<iasize; ii++) +			if (ip_compare(&addr, iaptr+ii, pfix)) +			{ +				free(iaptr); +				return SPF_PASS; +			} + +		free(iaptr); +		return SPF_FAIL; +	} + + +	if (strncasecmp(name, "mx", 2) == 0 && +	    (name[2] == 0 || name[2] == ':' || name[2] == '/')) +	{ +		char *domain_spec; +		int pfix; +		int rc; +		struct rfc1035_mxlist *mxlist, *mxp; +		RFC1035_ADDR addr; + +		/* +		** This mechanism matches if the <sending-host> is one of the +		** MX hosts for a domain name. +    +		** MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ] +     +		** SPF clients first perform an MX lookup on the <target-name>. +		** SPF clients then perform an A lookup on each MX name +		** returned, in order of MX priority.  The <sending-host> is +		** compared to each returned IP address.  If any address +		** matches, the mechanism matches. +		*/ + +		get_domain_pfix(info, name+2, &domain_spec, &pfix); + +		if (!domain_spec) +			return SPF_ERROR; + +		if (rfc1035_aton(info->tcpremoteip, &addr) < 0) +		{ +			free(domain_spec); +			set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +				    "Invalid tcpremoteip.\n"); +			return SPF_FAIL; +		} + +		rc=rfc1035_mxlist_create_x(&info->res, +					   domain_spec, 0, +					   &mxlist); +		free(domain_spec); +		if (rc) +		{ +			rfc1035_mxlist_free(mxlist); +			set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +				    "DNS MX lookup failed.\n"); +			return SPF_ERROR; +		} + +		for (mxp=mxlist; mxp; mxp=mxp->next) +		{ +			RFC1035_ADDR addrcmp; + +			if (rfc1035_sockaddrip(&mxp->address, +					       sizeof(mxp->address), +					       &addrcmp) < 0) +				continue; + +			if (ip_compare(&addr, &addrcmp, pfix)) +			{ +				rfc1035_mxlist_free(mxlist); +				return SPF_PASS; +			} +		} +		rfc1035_mxlist_free(mxlist); +		return SPF_FAIL; +	} + +	if (strncasecmp(name, "ip4:", 4) == 0) +	{ +		if (strchr(name+4, ':')) +			return SPF_FAIL; /* What does IPv6 addr doing here? */ + +#if RFC1035_IPV6 +		return do_ipcheck(name, info, 12*8); +#else +		return do_ipcheck(name, info, 0); +#endif +	} + +	if (strncasecmp(name, "ip6:", 4) == 0) +	{ +#if RFC1035_IPV6 +		return do_ipcheck(name, info, 0); +#else +		return SPF_FAIL; +#endif +	} + +	if (strncasecmp(name, "ptr", 3) == 0 && +	    (name[3] == 0 || name[3] == ':')) +	{ +		return do_ptr(name+3, info); +	} + +	if (strncasecmp(name, "exists:", 7) == 0) +	{ +		char *domain_spec; +		RFC1035_ADDR *iaptr; +		unsigned iasize; +		int rc; + +		/* +		** This mechanism is used to construct an arbitrary host name +		** that is used for a DNS A record query.  It allows for +		** complicated schemes involving arbitrary parts of the mail +		** envelope to determine what is legal. +		** +		** exists = "exists" ":" domain-spec +		** +		** The domain-spec is expanded as per Section 7.  The +		** resulting domain name is used for a DNS A lookup.  If any +		** A record is returned, this mechanism matches.  The lookup +		** type is 'A' even when the connection type is IPv6. +		*/ + +		domain_spec=expand(name+7, info); +		if (!domain_spec) +			return SPF_ERROR; + +		rc=rfc1035_a(&info->res, +			     domain_spec, +			     &iaptr, +			     &iasize); +		free(domain_spec); + +		if (rc < 0) +			return SPF_FAIL; +		if (rc > 0) +			return SPF_ERROR; +		free(iaptr); +		return SPF_PASS; +	} + +	return SPF_FAIL; +} + +static void setexp(const char *exp, +		   struct rfc1035_spf_info *info) +{ +	struct	rfc1035_reply *reply; +	char	namebuf[RFC1035_MAXNAMESIZE+1]; +	char	txtbuf[256]; +	int n; +	char	*str; + +	/* +	** The argument to the explanation modifier is a domain-spec +	** to be TXT queried.  The result of the TXT query is a +	** macro-string that is macro-expanded.  If SPF processing +	** results in a rejection, the expanded result SHOULD be +	** shown to the sender in the SMTP reject message.  This +	** string allows the publishing domain to communicate further +	** information via the SMTP receiver to legitimate senders in +	** the form of a short message or URL. +	*/ + + +	namebuf[0]=0; +	strncat(namebuf, exp, RFC1035_MAXNAMESIZE); + +	if (rfc1035_resolve_cname(&info->res, namebuf, +				  RFC1035_TYPE_TXT, RFC1035_CLASS_IN, +				  &reply, 0) < 0 || +	    reply == 0 || +	    (n=rfc1035_replysearch_an(&info->res, +				      reply, namebuf, RFC1035_TYPE_TXT, +				      RFC1035_CLASS_IN, 0)) < 0) +	{ +		set_err_msg(info->errmsg_buf, +			    info->errmsg_buf_size, +			    "A DNS lookup error occured while" +			    " fetching the SPF explanation record."); +	} +	else +	{ +		rfc1035_rr_gettxt(reply->allrrs[n], 0, txtbuf); + +		str=expand(txtbuf, info); + +		set_err_msg(info->errmsg_buf, +			    info->errmsg_buf_size, +			    str ? str:strerror(errno)); +		if (str) +			free(str); +	} +	rfc1035_replyfree(reply); +} + +static char lookup(struct rfc1035_spf_info *info) +{ +	char record[256]; +	char c; + +	/* +	**  +	** If a loop is detected, or if more than 20 subqueries are triggered, +	** an SPF client MAY abort the lookup and return the result "unknown". +	*/ + +	if (++*info->lookup_cnt > 20) +	{ +		set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +			    "Maximum of 20 nested SPF queries exceeded."); +		return SPF_UNKNOWN; +	} + +	c=rfc1035_spf_gettxt(info->current_domain, record); + +	if (c != SPF_PASS) +		return c; + +	return rfc1035_spf_compute(record, info); +} + +/* +** +** Certain directives perform macro interpolation on their arguments. +** +** Two passes: count # of chars in the expanded macro, generate the macro. +*/ + +static int do_expand(const char *str, struct rfc1035_spf_info *info, +		     void (*cb_func)(const char *, size_t n, void *), +		     void *void_arg); + +static void do_count(const char *p, size_t n, void *va) +{ +	*(size_t *)va += n; +} + +static void do_save(const char *p, size_t n, void *va) +{ +	char **b=(char **)va; + +	memcpy(*b, p, n); +	*b += n; +} + +static char *expand(const char *str, +		    struct rfc1035_spf_info *info) +{ +	size_t cnt=1; +	char *buf; +	char *p; + +	if (do_expand(str, info, do_count, &cnt) < 0) +		return NULL; + +	buf=malloc(cnt); + +	if (!buf) +	{ +		set_err_msg(info->errmsg_buf, info->errmsg_buf_size, +			    strerror(errno)); +		return NULL; +	} + +	p=buf; +	if (do_expand(str, info, do_save, &p) < 0) +	{ +		free(buf); +		return NULL; +	} + +	*p=0; +	return buf; +} + +static char *get_macro(struct rfc1035_spf_info *info, char name); + +static char *transform(char *macro, +		       unsigned transformer_count, +		       char transformer_reverse, +		       char delimiter_char); + +static int do_expand(const char *str, struct rfc1035_spf_info *info, +		      void (*cb_func)(const char *, size_t, void *), +		      void *void_arg) +{ +	unsigned char alpha, lalpha; +	unsigned transformer_count; +	char transformer_reverse; +	char delimiter_char; +	char *macro; + +	/* +	**     macro-string = *( macro-char / VCHAR ) +	**     macro-char   = ( "%{" ALPHA transformer *delimiter "}" ) +	**                    / "%%" / "%_" / "%-" +	**     transformer  = [ *DIGIT ] [ "r" ] +	**     delimiter    = "." / "-" / "+" / "," / "/" / "_" / "=" +	** +	*/ + +	while (*str) +	{ +		size_t i; + +		for (i=0; str[i]; i++) +			if (str[i] == '%') +				break; + +		if (i) +		{ +			(*cb_func)(str, i, void_arg); +			str += i; +			continue; +		} + +		/* +		**   A literal "%" is expressed by "%%". +		**   %_ expands to a single " " space. +		**   %- expands to a URL-encoded space, viz. "%20". +		*/ + +		switch (str[i+1]) { +		case '{': +			break; +		case '%': +			(*cb_func)("%", 1, void_arg); +			str += 2; +			continue; +		case '_': +			(*cb_func)(" ", 1, void_arg); +			str += 2; +			continue; +		case '-': +			(*cb_func)("%20", 3, void_arg); +			str += 2; +			continue; +		default: +			++str; +			continue; +		} + +		str += 2; + +		if (!*str) +			continue; +		alpha=(unsigned char)*str++; +		transformer_count=0; +		while (*str && isdigit((unsigned char)*str)) +		{ +			transformer_count=transformer_count * 10 + +				(*str++ - '0'); +		} + +		transformer_reverse=0; +		delimiter_char=0; + +		while (*str && *str != '}') +		{ +			switch (*str) { +			case 'r': +			case 'R': +				transformer_reverse='r'; +				break; +			case '.': +			case '-': +			case '+': +			case ',': +			case '/': +			case '_': +			case '=': +				delimiter_char= *str; +				break; +			} +			++str; +		} +		lalpha=tolower(alpha); + +		macro=get_macro(info, lalpha); +		if (macro && (transformer_reverse || transformer_count)) +		{ +			char *new_macro=transform(macro, transformer_count, +						  transformer_reverse, +						  delimiter_char); + +			free(macro); +			macro=new_macro; +		} + +		if (macro && lalpha != alpha) +		{ +			static const char validchars[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +			size_t l=1; +			size_t i,j; +			char *p; + +			for (i=0; macro[i]; i++) +			{ +				++l; +				if (strchr(validchars, macro[i]) == NULL) +					l += 2; +			} + +			p=malloc(l); +			for (i=j=0; p && macro[i]; i++) +			{ +				if (strchr(validchars, macro[i])) +				{ +					p[j]=macro[i]; +					++j; +				} +				else +				{ +					sprintf(p+j, "%%%02X", +						(int)(unsigned char)macro[i]); +					j += 3; +				} +			} +			if (p) +				p[j]=0; +			free(macro); +			macro=p; +		} + +		if (macro == NULL) +		{ +			set_err_msg(info->errmsg_buf, +				    info->errmsg_buf_size, +				    strerror(errno)); +			return -1; +		} + +		(*cb_func)(macro, strlen(macro), void_arg); +		free(macro); +		if (*str == '}') +			++str; +	} +	return 0; +} + +static char *expandc(const char *ipaddr); +static char *expandi(const char *ipaddr); + +static char *get_macro(struct rfc1035_spf_info *info, char name) +{ +	char *p; +	const char *cp; + +	switch (name) { +	case 'l': +		/* l = local-part of responsible-sender */ + +		cp=strrchr(info->mailfrom, '@'); +		p=malloc(cp-info->mailfrom+1); +		if (!p) +			return p; +		memcpy(p, info->mailfrom, cp-info->mailfrom); +		p[cp-info->mailfrom]=0; +		return p; +	case 's': +		/* s = responsible-sender */ +		return strdup(info->mailfrom); +	case 'o': +		return strdup(strrchr(info->mailfrom, '@')+1); +		/* o = responsible-domain */ +	case 'd': +		return strdup(info->current_domain); +		/* d = current-domain */ +	case 'c': +		return expandc(info->tcpremoteip); +		/* c = SMTP client IP (easily readable format) */ +	case 'i': +		return expandi(info->tcpremoteip); +		/* i = SMTP client IP (nibble format when an IPv6 address) */ +	case 'p': +		return strdup(info->tcpremotehost); +		/* p = SMTP client domain name */ +	case 'v': +		return (strdup(strchr(info->tcpremoteip, ':') && +			       strncmp(info->tcpremoteip, "::ffff:", 7) +			       ? "ip6":"in-addr")); +		/* v = client IP version string: "in-addr" for ipv4 or "ip6" for ipv6 */ +	case 'h': +	/* h = HELO/EHLO domain */ +		return strdup(info->helodomain); +	case 'r': +	/* r = receiving domain */ +		return strdup(info->mydomain); +	} + +	return strdup(""); +} + +/* +** +**   For IPv4 addresses, both the "i" and "c" macros expand to the +**   standard dotted-quad format. +** +**   For IPv6 addresses, the "i" macro expands to dot-format address; it +**   is intended for use in %{ir}.  The "c" macro may expand to any of +**   the hexadecimal colon-format addresses specified in [RFC3513] section +**   2.2.  It is intended for humans to read. +*/ + +static char *expandc(const char *ipaddr) +{ +	if (strncmp(ipaddr, "::ffff:", 7) == 0) +		return strdup(ipaddr+7); +	return strdup(ipaddr); +} + +static char *expandi(const char *ipaddr) +{ +	if (strchr(ipaddr, ':') && +	    strncmp(ipaddr, "::ffff:", 7)) +	{ +		RFC1035_ADDR addr; + +		if (rfc1035_aton(ipaddr, &addr) == 0) +		{ +			char name[sizeof(addr)*4+1]; +			char *p=name; +			int i; +			unsigned char *q=(unsigned char *)&addr; + +			for (i=0; i<sizeof(addr); i++) +			{ +				sprintf(p, "%s%x.%x", i ? ".":"", +					(int)((q[i] >> 4) & 0x0F), +					(int)(q[i] & 0x0F)); +				p += strlen(p); +			} +			return strdup(name); +		} + +	} +	return expandc(ipaddr); +} + +/* +**   If transformers or delimiters are provided, the macro strings are +**   split into parts.  After performing any reversal operation or +**   removal of left-hand parts, the parts are rejoined using "." and not +**   the original splitting characters. +*/ + +static unsigned tsplit(char *macro, char delimiter, char **wordptr) +{ +	/* Two passes */ +	unsigned cnt=0; + +	if (!delimiter) +		delimiter='.'; + +	while (*macro) +	{ +		++cnt; + +		if (wordptr) +			*wordptr++=macro; + +		while (*macro && *macro != delimiter) +			++macro; + +		if (*macro) +		{ +			if (wordptr) +				*macro=0; +			++macro; +		} + +	} +	return cnt; +}		 + +static char *transform(char *macro, +		       unsigned transformer_count, +		       char transformer_reverse, +		       char delimiter_char) +{ +	char **words; +	unsigned n=tsplit(macro, delimiter_char, NULL); +	unsigned start; +	unsigned i; +	char *buf; +	size_t len; + +	if ((words=malloc(sizeof(char *)*(n+1))) == NULL) +		return NULL; +	tsplit(macro, delimiter_char, words); +	words[n]=NULL; + +	/* +	** The DIGIT transformer indicates the number of right-hand parts to +	** use after optional reversal.  If a DIGIT is specified, it MUST be +	** nonzero.  If no DIGITs are specified, or if the value specifies more +	** parts than are available, all the available parts are used.  If the +	** DIGIT was 5, and only 3 parts were available, the macro interpreter +	** would pretend the DIGIT was 3.  Implementations MAY limit the +	** number, but MUST support at least a value of 9. +	*/ + +	if (transformer_count > n || transformer_count <= 0) +		transformer_count=n; + +	if (transformer_reverse) +	{ +		start=0; +		n=transformer_count; +	} +	else +	{ +		start=n-transformer_count; +	} + +	len=1; + +	for (i=start; i<n; i++) +	{ +		len += strlen(words[i])+1; +	} + +	buf=malloc(len); +	if (!buf) +	{ +		free(words); +		return NULL; +	} + +	*buf=0; +	if (transformer_reverse) +	{ +		for (i=n; i>start; ) +		{ +			if (*buf) +				strcat(buf, "."); +			strcat(buf, words[--i]); +		} +	} +	else +	{ +		for (i=start; i<n; i++) +		{ +			if (*buf) +				strcat(buf, "."); +			strcat(buf, words[i]); +		} +	} +	free(words); +	return buf; +} | 
