]> source.dussan.org Git - rspamd.git/commitdiff
* Fixes to spf parser:
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Tue, 8 Dec 2009 16:29:26 +0000 (19:29 +0300)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Tue, 8 Dec 2009 16:29:26 +0000 (19:29 +0300)
  - 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
src/spf.h

index c7243e89c89492332edced324a210c9180a297ae..7cb5ffe93802a533c8d7dc0c3c745b502d279c29 100644 (file)
--- 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);
index a47c4e8cb351ef3f4962f1b1cafced8b79911735..ab15cb91180d02fdd39795ded06a459009c14867 100644 (file)
--- 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;
 };