diff options
| author | Sam Varshavchik | 2013-08-19 16:39:41 -0400 |
|---|---|---|
| committer | Sam Varshavchik | 2013-08-25 14:43:51 -0400 |
| commit | 9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch) | |
| tree | 7a81a04cb51efb078ee350859a64be2ebc6b8813 /rfc1035/spf.c | |
| parent | a9520698b770168d1f33d6301463bb70a19655ec (diff) | |
| download | courier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2 | |
Initial checkin
Imported from subversion report, converted to git. Updated all paths in
scripts and makefiles, reflecting the new directory hierarchy.
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; +} |
