From ee38064d188a2415956256ba19fbba8fefd61476 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Tue, 8 Dec 2009 19:29:26 +0300 Subject: [PATCH] * Fixes to spf parser: - add macros support - fix include command - fix exists command - add handling of DNS errors - fix all records in include parts - fix some issues with ip masks --- src/spf.c | 349 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/spf.h | 5 + 2 files changed, 342 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); diff --git a/src/spf.h b/src/spf.h index a47c4e8cb..ab15cb911 100644 --- a/src/spf.h +++ b/src/spf.h @@ -42,8 +42,13 @@ struct spf_record { GList *addrs; char *cur_domain; + char *sender; + char *sender_domain; + char *local_part; struct worker_task *task; spf_cb_t callback; + + gboolean in_include; }; -- 2.39.5