aboutsummaryrefslogtreecommitdiffstats
path: root/src/spf.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/spf.c')
-rw-r--r--src/spf.c349
1 files changed, 337 insertions, 12 deletions
diff --git a/src/spf.c b/src/spf.c
index c7243e89c..7cb5ffe93 100644
--- a/src/spf.c
+++ b/src/spf.c
@@ -215,7 +215,7 @@ parse_spf_hostmask (struct worker_task *task, const char *begin, struct spf_addr
else {
addr->mask = 32;
if (host == NULL) {
- g_strlcpy (host, begin, strlen (begin));
+ host = memory_pool_strdup (task->task_pool, begin);
}
}
@@ -228,7 +228,7 @@ spf_record_dns_callback (int result, char type, int count, int ttl, void *addres
struct spf_dns_cb *cb = data;
char *begin;
struct evdns_mx *mx;
- GList *tmp = NULL;
+ GList *tmp = NULL, *elt, *last;
if (result == DNS_ERR_NONE) {
if (addresses != NULL) {
@@ -275,9 +275,25 @@ spf_record_dns_callback (int result, char type, int count, int ttl, void *addres
tmp = cb->rec->addrs;
cb->rec->addrs = NULL;
}
+ cb->rec->in_include = TRUE;
start_spf_parse (cb->rec, begin);
+ cb->rec->in_include = FALSE;
+
if (tmp) {
- cb->rec->addrs = g_list_concat (tmp, cb->rec->addrs);
+ elt = g_list_find (tmp, cb->addr);
+ if (elt) {
+ /* Insert new list in place of include element */
+ last = g_list_last (cb->rec->addrs);
+
+ elt->prev->next = cb->rec->addrs;
+ elt->next->prev = last;
+
+ cb->rec->addrs->prev = elt->prev;
+ last->next = elt->next;
+
+ cb->rec->addrs = tmp;
+ g_list_free1 (elt);
+ }
}
}
}
@@ -288,11 +304,45 @@ spf_record_dns_callback (int result, char type, int count, int ttl, void *addres
if (type == DNS_IPv4_A) {
/* If specified address resolves, we can accept connection from every IP */
cb->addr->addr = ntohl (INADDR_ANY);
+ cb->addr->mask = 0;
}
break;
}
}
}
+ else if (result == DNS_ERR_NOTEXIST) {
+ switch (cb->cur_action) {
+ case SPF_RESOLVE_MX:
+ if (type == DNS_MX) {
+ msg_info ("spf_dns_callback: cannot find MX record for %s", cb->rec->cur_domain);
+ cb->addr->addr = ntohl (INADDR_NONE);
+ }
+ else if (type == DNS_IPv4_A) {
+ msg_info ("spf_dns_callback: cannot resolve MX record for %s", cb->rec->cur_domain);
+ cb->addr->addr = ntohl (INADDR_NONE);
+ }
+ break;
+ case SPF_RESOLVE_A:
+ if (type == DNS_IPv4_A) {
+ /* XXX: process only one record */
+ cb->addr->addr = ntohl (INADDR_NONE);
+ }
+ break;
+ case SPF_RESOLVE_PTR:
+ break;
+ case SPF_RESOLVE_REDIRECT:
+ msg_info ("spf_dns_callback: cannot resolve TXT record for redirect action");
+ break;
+ case SPF_RESOLVE_INCLUDE:
+ msg_info ("spf_dns_callback: cannot resolve TXT record for include action");
+ break;
+ case SPF_RESOLVE_EXP:
+ break;
+ case SPF_RESOLVE_EXISTS:
+ cb->addr->addr = ntohl (INADDR_NONE);
+ break;
+ }
+ }
cb->rec->task->save.saved--;
if (cb->rec->task->save.saved == 0 && cb->rec->callback) {
@@ -392,8 +442,15 @@ static gboolean
parse_spf_all (struct worker_task *task, const char *begin, struct spf_record *rec, struct spf_addr *addr)
{
/* All is 0/0 */
- addr->addr = 0;
- addr->mask = 0;
+ if (rec->in_include) {
+ /* Ignore all record in include */
+ addr->addr = 0;
+ addr->mask = 32;
+ }
+ else {
+ addr->addr = 0;
+ addr->mask = 0;
+ }
return TRUE;
}
@@ -424,7 +481,7 @@ parse_spf_include (struct worker_task *task, const char *begin, struct spf_recor
cb = memory_pool_alloc (task->task_pool, sizeof (struct spf_dns_cb));
cb->rec = rec;
cb->addr = addr;
- cb->cur_action = SPF_RESOLVE_REDIRECT;
+ cb->cur_action = SPF_RESOLVE_INCLUDE;
domain = memory_pool_strdup (task->task_pool, begin);
if (evdns_resolve_txt (domain, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
@@ -490,6 +547,7 @@ parse_spf_exists (struct worker_task *task, const char *begin, struct spf_record
begin ++;
rec->dns_requests ++;
+ addr->mask = 32;
cb = memory_pool_alloc (task->task_pool, sizeof (struct spf_dns_cb));
cb->rec = rec;
cb->addr = addr;
@@ -506,6 +564,259 @@ parse_spf_exists (struct worker_task *task, const char *begin, struct spf_record
return FALSE;
}
+static void
+reverse_spf_ip (char *ip, int len)
+{
+ char ipbuf[sizeof("255.255.255.255") - 1], *p, *c;
+ int t = 0, l = len;
+
+ if (len > sizeof (ipbuf)) {
+ msg_info ("reverse_spf_ip: cannot reverse string of length %d", len);
+ return;
+ }
+
+ p = ipbuf + len;
+ c = ip;
+ while (-- l) {
+ if (*c == '.') {
+ memcpy (p, c - t, t);
+ *--p = '.';
+ c ++;
+ t = 0;
+ continue;
+ }
+
+ t ++;
+ c ++;
+ p --;
+ }
+
+ memcpy (p - 1, c - t, t + 1);
+
+ memcpy (ip, ipbuf, len);
+}
+
+static char *
+expand_spf_macro (struct worker_task *task, struct spf_record *rec, char *begin)
+{
+ char *p, *c, *new, *tmp;
+ int len = 0, slen = 0, state = 0;
+
+ p = begin;
+ /* Calculate length */
+ while (*p) {
+ switch (state) {
+ case 0:
+ /* Skip any character and wait for % in input */
+ if (*p == '%') {
+ state = 1;
+ }
+ else {
+ len ++;
+ }
+
+ slen ++;
+ p ++;
+ break;
+ case 1:
+ /* We got % sign, so we should whether wait for { or for - or for _ or for % */
+ if (*p == '%' || *p == '-') {
+ /* Just a single % sign or space */
+ len ++;
+ }
+ else if (*p == '_') {
+ /* %20 */
+ len += sizeof ("%20") - 1;
+ }
+ else if (*p == '{') {
+ state = 2;
+ }
+ else {
+ /* Something unknown */
+ msg_info ("expand_spf_macro: bad spf element: %s", begin);
+ return begin;
+ }
+ p ++;
+ slen ++;
+ break;
+ case 2:
+ /* Read macro name */
+ switch (*p) {
+ case 'i':
+ len += sizeof ("255.255.255.255") - 1;
+ break;
+ case 's':
+ len += strlen (rec->sender);
+ break;
+ case 'l':
+ len += strlen (rec->local_part);
+ break;
+ case 'o':
+ len += strlen (rec->sender_domain);
+ break;
+ case 'd':
+ len += strlen (rec->cur_domain);
+ break;
+ case 'v':
+ len += sizeof ("in-addr") - 1;
+ break;
+ case 'h':
+ if (task->helo) {
+ len += strlen (task->helo);
+ }
+ break;
+ default:
+ msg_info ("expand_spf_macro: unknown or unsupported spf macro %c in %s", *p, begin);
+ return begin;
+ }
+ p ++;
+ slen ++;
+ state = 3;
+ break;
+ case 3:
+ /* Read modifier */
+ if (*p == '}') {
+ state = 0;
+ }
+ else if (*p != 'r' && !g_ascii_isdigit (*p)) {
+ msg_info ("expand_spf_macro: unknown or unsupported spf modifier %c in %s", *p, begin);
+ return begin;
+ }
+ p ++;
+ slen ++;
+ break;
+ }
+ }
+
+ if (slen == len) {
+ /* No expansion needed */
+ return begin;
+ }
+
+ new = memory_pool_alloc (task->task_pool, len + 1);
+
+ c = new;
+ p = begin;
+ state = 0;
+ /* Begin macro expansion */
+
+ while (*p) {
+ switch (state) {
+ case 0:
+ /* Skip any character and wait for % in input */
+ if (*p == '%') {
+ state = 1;
+ }
+ else {
+ *c = *p;
+ c ++;
+ }
+
+ p ++;
+ break;
+ case 1:
+ /* We got % sign, so we should whether wait for { or for - or for _ or for % */
+ if (*p == '%') {
+ /* Just a single % sign or space */
+ *c++ = '%';
+ }
+ else if (*p == '-') {
+ *c++ = ' ';
+ }
+ else if (*p == '_') {
+ /* %20 */
+ *c++ = '%';
+ *c++ = '2';
+ *c++ = '0';
+ }
+ else if (*p == '{') {
+ state = 2;
+ }
+ else {
+ /* Something unknown */
+ msg_info ("expand_spf_macro: bad spf element: %s", begin);
+ return begin;
+ }
+ p ++;
+ break;
+ case 2:
+ /* Read macro name */
+ switch (*p) {
+ case 'i':
+ tmp = inet_ntoa (task->from_addr);
+ len = strlen (tmp);
+ memcpy (c, tmp, len);
+ c += len;
+ break;
+ case 's':
+ len = strlen (rec->sender);
+ memcpy (c, rec->sender, len);
+ c += len;
+ break;
+ case 'l':
+ len = strlen (rec->local_part);
+ memcpy (c, rec->local_part, len);
+ c += len;
+ break;
+ case 'o':
+ len = strlen (rec->sender_domain);
+ memcpy (c, rec->sender_domain, len);
+ c += len;
+ break;
+ case 'd':
+ len = strlen (rec->cur_domain);
+ memcpy (c, rec->cur_domain, len);
+ c += len;
+ break;
+ case 'v':
+ len = sizeof ("in-addr") - 1;
+ memcpy (c, "in-addr", len);
+ c += len;
+ break;
+ case 'h':
+ if (task->helo) {
+ tmp = strchr (task->helo, '@');
+ if (tmp) {
+ len = strlen (tmp + 1);
+ memcpy (c, tmp + 1, len);
+ c += len;
+ }
+ }
+ break;
+ default:
+ msg_info ("expand_spf_macro: unknown or unsupported spf macro %c in %s", *p, begin);
+ return begin;
+ }
+ p ++;
+ state = 3;
+ break;
+ case 3:
+ /* Read modifier */
+ if (*p == '}') {
+ state = 0;
+ }
+ else if (*p == 'r' && len != 0) {
+ reverse_spf_ip (c - len, len);
+ len = 0;
+ }
+ else if (g_ascii_isdigit (*p)) {
+ /*XXX: try to implement domain strimming */
+ }
+ else {
+ msg_info ("expand_spf_macro: unknown or unsupported spf modifier %c in %s", *p, begin);
+ return begin;
+ }
+ p ++;
+ break;
+ }
+ }
+ /* Null terminate */
+ *c = '\0';
+ msg_info ("expanded string: %s", new);
+ return new;
+
+}
+
#define NEW_ADDR(x) do { \
(x) = memory_pool_alloc (task->task_pool, sizeof (struct spf_addr)); \
(x)->mech = check_spf_mech (rec->cur_elt, &need_shift); \
@@ -525,11 +836,7 @@ parse_spf_record (struct worker_task *task, struct spf_record *rec)
return FALSE;
}
else {
- /* Check spf mech */
- if (need_shift) {
- rec->cur_elt ++;
- }
- begin = rec->cur_elt;
+ begin = expand_spf_macro (task, rec, rec->cur_elt);
if (*begin == '?' || *begin == '+' || *begin == '-' || *begin == '~') {
begin ++;
}
@@ -561,8 +868,9 @@ parse_spf_record (struct worker_task *task, struct spf_record *rec)
res = parse_spf_ip4 (task, begin, rec, new);
}
else if (strncmp (begin, SPF_INCLUDE, sizeof (SPF_INCLUDE) - 1) == 0) {
+ NEW_ADDR (new);
begin += sizeof (SPF_INCLUDE) - 1;
- res = parse_spf_include (task, begin, rec, NULL);
+ res = parse_spf_include (task, begin, rec, new);
}
else {
msg_info ("parse_spf_record: bad spf command: %s", begin);
@@ -689,10 +997,18 @@ resolve_spf (struct worker_task *task, spf_cb_t callback)
rec->callback = callback;
if (task->from && (domain = strchr (task->from, '@'))) {
+ rec->sender = task->from;
+
+ rec->local_part = memory_pool_strdup (task->task_pool, task->from);
+ *(rec->local_part + (domain - task->from)) = '\0';
+ if (*rec->local_part == '<') {
+ memmove (rec->local_part, rec->local_part + 1, strlen (rec->local_part));
+ }
rec->cur_domain = memory_pool_strdup (task->task_pool, domain + 1);
if ((domain = strchr (rec->cur_domain, '>')) != NULL) {
*domain = '\0';
}
+ rec->sender_domain = rec->cur_domain;
if (evdns_resolve_txt (rec->cur_domain, DNS_QUERY_NO_SEARCH, spf_dns_callback, (void *)rec) == 0) {
task->save.saved++;
@@ -707,14 +1023,23 @@ resolve_spf (struct worker_task *task, spf_cb_t callback)
if (domains != NULL) {
rec->cur_domain = memory_pool_strdup (task->task_pool, domains->data);
g_list_free (domains);
+
if ((domain = strchr (rec->cur_domain, '@')) == NULL) {
return FALSE;
}
+ rec->sender = memory_pool_strdup (task->task_pool, rec->cur_domain);
+ rec->local_part = rec->cur_domain;
+ *domain = '\0';
rec->cur_domain = domain + 1;
+ if ((domain = strchr (rec->local_part, '<')) != NULL) {
+ memmove (rec->local_part, domain + 1, strlen (domain));
+ }
+
if ((domain = strchr (rec->cur_domain, '>')) != NULL) {
*domain = '\0';
}
+ rec->sender_domain = rec->cur_domain;
if (evdns_resolve_txt (rec->cur_domain, DNS_QUERY_NO_SEARCH, spf_dns_callback, (void *)rec) == 0) {
task->save.saved++;
register_async_event (task->s, (event_finalizer_t) spf_dns_callback, NULL, TRUE);