diff options
author | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2015-03-18 17:24:08 +0000 |
---|---|---|
committer | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2015-03-18 17:24:08 +0000 |
commit | 59cc1c9179db300edcfe5baf86755f129b6945bb (patch) | |
tree | 945dd74fba52b477beb89293fe8f98d0f8080ec0 | |
parent | 19d825e68e0da0e1ef9100d1c7162ba8c4d4824e (diff) | |
parent | cc27a6dcda6076d6db3a2bba740832c49dc11c2d (diff) | |
download | rspamd-59cc1c9179db300edcfe5baf86755f129b6945bb.tar.gz rspamd-59cc1c9179db300edcfe5baf86755f129b6945bb.zip |
Merge branch 'spf-rework'
-rw-r--r-- | src/libserver/spf.c | 1414 | ||||
-rw-r--r-- | src/libserver/spf.h | 69 | ||||
-rw-r--r-- | src/plugins/spf.c | 172 |
3 files changed, 787 insertions, 868 deletions
diff --git a/src/libserver/spf.c b/src/libserver/spf.c index 13eff0c27..4f0a40df5 100644 --- a/src/libserver/spf.c +++ b/src/libserver/spf.c @@ -49,8 +49,29 @@ #define SPF_MAX_NESTING 10 #define SPF_MAX_DNS_REQUESTS 30 +struct spf_resolved_element { + GPtrArray *elts; + gchar *cur_domain; + gboolean redirected; /* Ingnore level, it's redirected */ +}; + +struct spf_record { + gint nested; + gint dns_requests; + gint requests_inflight; + + guint ttl; + GArray *resolved; /* Array of struct spf_resolved_element */ + const gchar *sender; + const gchar *sender_domain; + gchar *local_part; + struct rspamd_task *task; + spf_cb_t callback; + gboolean done; +}; + /** - * State machine for SPF record: + * BNF for SPF record: * * spf_mech ::= +|-|~|? * @@ -75,12 +96,13 @@ struct spf_dns_cb { struct spf_record *rec; struct spf_addr *addr; + struct spf_resolved_element *resolved; gchar *ptr_host; spf_action_t cur_action; gboolean in_include; }; -#define CHECK_REC(rec) \ +#define CHECK_REC(rec) \ do { \ if ((rec)->nested > SPF_MAX_NESTING || \ (rec)->dns_requests > SPF_MAX_DNS_REQUESTS) { \ @@ -91,10 +113,8 @@ struct spf_dns_cb { } \ } while (0) \ -static gboolean parse_spf_record (struct rspamd_task *task, - struct spf_record *rec); -static gboolean start_spf_parse (struct spf_record *rec, gchar *begin, - guint ttl); +static gboolean parse_spf_record (struct spf_record *rec, const gchar *elt); +static gboolean start_spf_parse (struct spf_record *rec, gchar *begin); /* Determine spf mech */ static spf_mech_t @@ -119,99 +139,59 @@ check_spf_mech (const gchar *elt, gboolean *need_shift) } } -/* Debugging function that dumps spf record in log */ -static void -dump_spf_record (GList *addrs) +static struct spf_addr * +rspamd_spf_new_addr (struct spf_record *rec, const gchar *elt) { - struct spf_addr *addr; - GList *cur; - gint r = 0; - gchar logbuf[BUFSIZ], c; -#ifdef HAVE_INET_PTON - gchar ipbuf[INET6_ADDRSTRLEN]; -#else - struct in_addr ina; -#endif - - cur = addrs; - - while (cur) { - addr = cur->data; - if (!addr->is_list) { - switch (addr->mech) { - case SPF_FAIL: - c = '-'; - break; - case SPF_SOFT_FAIL: - case SPF_NEUTRAL: - c = '~'; - break; - case SPF_PASS: - c = '+'; - break; - } -#ifdef HAVE_INET_PTON - if (addr->data.normal.ipv6) { - inet_ntop (AF_INET6, &addr->data.normal.d.in6, ipbuf, - sizeof (ipbuf)); + struct spf_resolved_element *resolved; + gboolean need_shift = FALSE; + struct spf_addr *naddr; - } - else { - inet_ntop (AF_INET, &addr->data.normal.d.in4, ipbuf, - sizeof (ipbuf)); - } - r += snprintf (logbuf + r, - sizeof (logbuf) - r, - "%c%s/%d; ", - c, - ipbuf, - addr->data.normal.mask); -#else - ina.s_addr = addr->data.normal.d.in4.s_addr; - r += snprintf (logbuf + r, - sizeof (logbuf) - r, - "%c%s/%d; ", - c, - inet_ntoa (ina), - addr->data.normal.mask); -#endif - } - else { - r += snprintf (logbuf + r, - sizeof (logbuf) - r, - "%s; ", - addr->spf_string); - dump_spf_record (addr->data.list); - } - cur = g_list_next (cur); + /* Peek the top element */ + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); + naddr = g_slice_alloc0 (sizeof (*naddr)); + naddr->mech = check_spf_mech (elt, &need_shift); + + if (need_shift) { + naddr->spf_string = g_strdup (elt + 1); + } + else { + naddr->spf_string = g_strdup (elt); } - msg_info ("spf record: %s", logbuf); + + g_ptr_array_add (resolved->elts, naddr); + + return naddr; } -/* Find position of address inside addrs list */ -static GList * -spf_addr_find (GList *addrs, gpointer to_find) +static void +rspamd_spf_free_addr (gpointer a) { - struct spf_addr *addr; - GList *cur, *res = NULL; - - cur = addrs; - while (cur) { - addr = cur->data; - if (addr->is_list) { - if ((res = spf_addr_find (addr->data.list, to_find)) != NULL) { - return cur; - } - } - else { - if (cur->data == to_find) { - return cur; - } - } - cur = g_list_next (cur); + struct spf_addr *addr = a; + + if (addr) { + g_free (addr->spf_string); + g_slice_free1 (sizeof (*addr), addr); } +} + +static void +rspamd_spf_new_addr_list (struct spf_record *rec, const gchar *domain) +{ + struct spf_resolved_element resolved; + + resolved.redirected = FALSE; + resolved.cur_domain = g_strdup (domain); + resolved.elts = g_ptr_array_new_full (8, rspamd_spf_free_addr); + + g_array_append_val (rec->resolved, resolved); +} + +/* Debugging function that dumps spf record in log */ +static void +dump_spf_record (GList *addrs) +{ - return res; } /* @@ -221,262 +201,126 @@ static void spf_record_destructor (gpointer r) { struct spf_record *rec = r; - GList *cur; struct spf_addr *addr; + struct spf_resolved_element *elt; + guint i, j; - if (rec->addrs) { - cur = rec->addrs; - while (cur) { - addr = cur->data; - if (addr->is_list && addr->data.list != NULL) { - g_list_free (addr->data.list); - } - cur = g_list_next (cur); + if (rec) { + for (i = 0; i < rec->resolved->len; i ++) { + elt = &g_array_index (rec->resolved, struct spf_resolved_element, i); + + /* Elts are destructed automatically here */ + g_ptr_array_free (elt->elts, TRUE); + g_free (elt->cur_domain); } - g_list_free (rec->addrs); + g_array_free (rec->resolved, TRUE); } } -static gboolean -parse_spf_ipmask (const gchar *begin, - struct spf_addr *addr, - struct spf_record *rec) +static void +rspamd_flatten_record_dtor (struct spf_resolved *r) { - const gchar *pos; - gchar mask_buf[5] = {'\0'}, *p; - gint state = 0, dots = 0; -#ifdef HAVE_INET_PTON - gchar ip_buf[INET6_ADDRSTRLEN]; -#else - gchar ip_buf[INET_ADDRSTRLEN]; -#endif - - bzero (ip_buf, sizeof (ip_buf)); - bzero (mask_buf, sizeof (mask_buf)); - pos = begin; - p = ip_buf; + struct spf_addr *addr; + guint i; - while (*pos) { - switch (state) { - case 0: - /* Require ':' */ - if (*pos != ':') { - msg_info ("<%s>: spf error for domain %s: semicolon missing", - rec->task->message_id, rec->sender_domain); - return FALSE; - } - state = 1; - pos++; - p = ip_buf; - dots = 0; - break; - case 1: -#ifdef HAVE_INET_PTON - if (p - ip_buf >= (gint)sizeof (ip_buf)) { - return FALSE; - } - if (g_ascii_isxdigit (*pos)) { - *p++ = *pos++; - } - else if (*pos == '.' || *pos == ':') { - *p++ = *pos++; - dots++; - } -#else - /* Begin parse ip */ - if (p - ip_buf >= (gint)sizeof (ip_buf) || dots > 3) { - return FALSE; - } - if (g_ascii_isdigit (*pos)) { - *p++ = *pos++; - } - else if (*pos == '.') { - *p++ = *pos++; - dots++; - } -#endif - else if (*pos == '/') { - pos++; - p = mask_buf; - state = 2; - } - else { - /* Invalid character */ - msg_info ("<%s>: spf error for domain %s: invalid ip address", - rec->task->message_id, rec->sender_domain); - return FALSE; - } - break; - case 2: - /* Parse mask */ - if (p - mask_buf >= (gint)sizeof (mask_buf)) { - msg_info ("<%s>: spf error for domain %s: too long mask", - rec->task->message_id, rec->sender_domain); - return FALSE; - } - if (g_ascii_isdigit (*pos)) { - *p++ = *pos++; - } - else { - return FALSE; - } - break; - } + for (i = 0; i < r->elts; i ++) { + addr = &g_array_index (r->elts, struct spf_addr, i); + g_free (addr->spf_string); } -#ifdef HAVE_INET_PTON - if (inet_pton (AF_INET, ip_buf, &addr->data.normal.d.in4) != 1) { - if (inet_pton (AF_INET6, ip_buf, &addr->data.normal.d.in6) == 1) { - addr->data.normal.ipv6 = TRUE; - } - else { - msg_info ("<%s>: spf error for domain %s: invalid ip address", - rec->task->message_id, rec->sender_domain); - return FALSE; - } + g_free (r->domain); + g_array_free (r->elts, TRUE); + g_slice_free1 (sizeof (*r), r); +} + +static void +rspamd_spf_process_reference (struct spf_resolved *target, + struct spf_addr *addr, struct spf_record *rec, gboolean top) +{ + struct spf_resolved_element *elt; + struct spf_addr *cur, taddr; + guint i; + + if (addr) { + g_assert (addr->m.idx < rec->resolved->len); + + elt = &g_array_index (rec->resolved, struct spf_resolved_element, + addr->m.idx); } else { - addr->data.normal.ipv6 = FALSE; + elt = &g_array_index (rec->resolved, struct spf_resolved_element, 0); } -#else - if (!inet_aton (ip_buf, &addr->data.normal.d.in4)) { - return FALSE; + + while (elt->redirected) { + cur = g_ptr_array_index (elt->elts, 0); + g_assert (cur->flags & RSPAMD_SPF_FLAG_REFRENCE); + g_assert (cur->m.idx < rec->resolved->len); + elt = &g_array_index (rec->resolved, struct spf_resolved_element, + cur->m.idx); } -#endif - if (state == 2) { - /* Also parse mask */ - if (!addr->data.normal.ipv6) { - addr->data.normal.mask = strtoul (mask_buf, NULL, 10); - if (addr->data.normal.mask > 32) { - msg_info ( - "<%s>: spf error for domain %s: bad ipmask value: '%s'", - rec->task->message_id, - rec->sender_domain, - begin); - return FALSE; - } + + for (i = 0; i < elt->elts->len; i ++) { + cur = g_ptr_array_index (elt->elts, i); + + if (!(cur->flags & RSPAMD_SPF_FLAG_PARSED)) { + /* Ignore unparsed addrs */ + continue; + } + else if (cur->flags & RSPAMD_SPF_FLAG_REFRENCE) { + /* Process reference */ + rspamd_spf_process_reference (target, cur, rec, FALSE); } else { - addr->data.normal.mask = strtoul (mask_buf, NULL, 10); - if (addr->data.normal.mask > 128) { - msg_info ( - "<%s>: spf error for domain %s: bad ipmask value: '%s'", - rec->task->message_id, - rec->sender_domain, - begin); - return FALSE; + if ((cur->flags & RSPAMD_SPF_FLAG_ANY) && !top) { + /* Ignore wide policies in includes */ + continue; } + + memcpy (&taddr, cur, sizeof (taddr)); + /* Steal element */ + cur->spf_string = NULL; + g_array_append_val (target->elts, taddr); } } - else { - addr->data.normal.mask = addr->data.normal.ipv6 ? 128 : 32; - } - addr->data.normal.parsed = TRUE; - return TRUE; - } -static gchar * -parse_spf_hostmask (struct rspamd_task *task, - const gchar *begin, - struct spf_addr *addr, - struct spf_record *rec) +/* + * Parse record and flatten it to a simple structure + */ +static struct spf_resolved * +rspamd_spf_record_flatten (struct spf_record *rec) { - gchar *host = NULL, *p, mask_buf[3]; - gint hostlen; - - bzero (mask_buf, sizeof (mask_buf)); - if (*begin == '\0' || *begin == '/') { - /* Assume host as host to resolve from record */ - host = rec->cur_domain; - } - p = strchr (begin, '/'); - if (p != NULL) { - /* Extract mask */ - rspamd_strlcpy (mask_buf, p + 1, sizeof (mask_buf)); - addr->data.normal.mask = strtoul (mask_buf, NULL, 10); - if (addr->data.normal.mask > 32) { - msg_info ("<%s>: spf error for domain %s: too long mask", - rec->task->message_id, rec->sender_domain); - return FALSE; - } - if (host == NULL) { - hostlen = p - begin; - host = rspamd_mempool_alloc (task->task_pool, hostlen); - rspamd_strlcpy (host, begin, hostlen); - } - } - else { - addr->data.normal.mask = 32; - if (host == NULL) { - host = rspamd_mempool_strdup (task->task_pool, begin); - } - } + struct spf_resolved *res; - return host; + struct spf_resolved_element *top; + guint i; + + g_assert (rec != NULL); + + res = g_slice_alloc (sizeof (*res)); + res->elts = g_array_sized_new (FALSE, FALSE, sizeof (struct spf_addr), + rec->resolved->len); + res->domain = g_strdup (rec->sender_domain); + res->ttl = rec->ttl; + REF_INIT_RETAIN (res, rspamd_flatten_record_dtor); + + top = &g_array_index (rec->resolved, struct spf_resolved_element, 0); + + rspamd_spf_process_reference (res->elts, NULL, rec, TRUE); + + return res; } static void -spf_record_process_addr (struct rdns_reply_entry *elt, - struct spf_dns_cb *cb, struct rspamd_task *task) +rspamd_spf_maybe_return (struct spf_record *rec) { - struct spf_addr *addr = cb->addr, *new_addr; - GList *tmp = NULL; + struct spf_resolved *flat; - if (elt->type == RDNS_REQUEST_A) { - if (!addr->data.normal.parsed) { - addr->data.normal.d.in4.s_addr = elt->content.a.addr.s_addr; - addr->data.normal.parsed = TRUE; - } - else { - /* Insert one more address */ - tmp = spf_addr_find (cb->rec->addrs, addr); - if (tmp) { - new_addr = rspamd_mempool_alloc (task->task_pool, - sizeof (struct spf_addr)); - memcpy (new_addr, addr, sizeof (struct spf_addr)); - new_addr->data.normal.d.in4.s_addr = elt->content.a.addr.s_addr; - new_addr->data.normal.parsed = TRUE; - cb->rec->addrs = g_list_insert_before (cb->rec->addrs, - tmp, - new_addr); - } - else { - msg_info ("<%s>: spf error for domain %s: addresses mismatch", - task->message_id, cb->rec->sender_domain); - } - } - - } - else if (elt->type == RDNS_REQUEST_AAAA) { - if (!addr->data.normal.parsed) { - memcpy (&addr->data.normal.d.in6, - &elt->content.aaa.addr, sizeof (struct in6_addr)); - addr->data.normal.mask = 32; - addr->data.normal.parsed = TRUE; - addr->data.normal.ipv6 = TRUE; - } - else { - /* Insert one more address */ - tmp = spf_addr_find (cb->rec->addrs, addr); - if (tmp) { - new_addr = - rspamd_mempool_alloc (task->task_pool, - sizeof (struct spf_addr)); - memcpy (new_addr, addr, sizeof (struct spf_addr)); - memcpy (&new_addr->data.normal.d.in6, - &elt->content.aaa.addr, sizeof (struct in6_addr)); - new_addr->data.normal.parsed = TRUE; - new_addr->data.normal.ipv6 = TRUE; - cb->rec->addrs = g_list_insert_before (cb->rec->addrs, - tmp, - new_addr); - } - else { - msg_info ("<%s>: spf error for domain %s: addresses mismatch", - task->message_id, cb->rec->sender_domain); - } - } + if (rec->requests_inflight == 0 && !rec->done) { + flat = rspamd_spf_record_flatten (rec); + rec->callback (flat, rec->task); + REF_RELEASE (flat); + rec->done = TRUE; } } @@ -493,7 +337,7 @@ spf_check_ptr_host (struct spf_dns_cb *cb, const char *name) } else { - dstart = cb->rec->cur_domain; + dstart = cb->resolved->cur_domain; } msg_debug ("check ptr %s vs %s", name, dstart); @@ -517,7 +361,7 @@ spf_check_ptr_host (struct spf_dns_cb *cb, const char *name) /* Now compare from end to start */ for (;;) { if (g_ascii_tolower (*dend) != g_ascii_tolower (*nend)) { - msg_debug ("ptr records missmatch: %s and %s", dend, nend); + msg_debug ("ptr records mismatch: %s and %s", dend, nend); return FALSE; } if (dend == dstart) { @@ -539,6 +383,49 @@ spf_check_ptr_host (struct spf_dns_cb *cb, const char *name) } static void +spf_record_process_addr (struct spf_addr *addr, struct rdns_reply_entry *reply) +{ + if (reply->type == RDNS_REQUEST_AAAA) { + memcpy (addr->addr6, &reply->content.aaa.addr, sizeof (addr->addr6)); + addr->flags |= RSPAMD_SPF_FLAG_IPV6; + } + else if (reply->type == RDNS_REQUEST_A) { + memcpy (addr->addr4, &reply->content.a.addr, sizeof (addr->addr4)); + addr->flags |= RSPAMD_SPF_FLAG_IPV4; + } + else { + msg_err ("internal error, bad DNS reply is treated as address: %s", + rdns_strtype (reply->type)); + } +} + +static void +spf_record_addr_set (struct spf_addr *addr, gboolean allow_any) +{ + guchar fill; + guint maskv4, maskv6; + + if (allow_any) { + fill = 0; + maskv4 = 0; + maskv6 = 0; + } + else { + fill = 0xff; + maskv4 = 32; + maskv6 = 128; + } + + memset (addr->addr4, fill, sizeof (addr->addr4)); + memset (addr->addr6, fill, sizeof (addr->addr6)); + addr->m.dual.mask_v4 = maskv4; + addr->m.dual.mask_v6 = maskv6; + + addr->flags |= RSPAMD_SPF_FLAG_IPV4; + addr->flags |= RSPAMD_SPF_FLAG_IPV6; +} + +static void spf_record_dns_callback (struct rdns_reply *reply, gpointer arg) { struct spf_dns_cb *cb = arg; @@ -546,11 +433,13 @@ spf_record_dns_callback (struct rdns_reply *reply, gpointer arg) struct rdns_reply_entry *elt_data; GList *tmp = NULL; struct rspamd_task *task; + struct spf_addr *addr; gboolean ret; task = cb->rec->task; cb->rec->requests_inflight--; + addr = cb->addr; if (reply->code == RDNS_RC_NOERROR) { /* Add all logic for all DNS states here */ @@ -578,12 +467,12 @@ spf_record_dns_callback (struct rdns_reply *reply, gpointer arg) } } else { - spf_record_process_addr (elt_data, cb, task); + spf_record_process_addr (addr, elt_data); } break; case SPF_RESOLVE_A: case SPF_RESOLVE_AAA: - spf_record_process_addr (elt_data, cb, task); + spf_record_process_addr (addr, elt_data); break; case SPF_RESOLVE_PTR: if (elt_data->type == RDNS_REQUEST_PTR) { @@ -610,46 +499,19 @@ spf_record_dns_callback (struct rdns_reply *reply, gpointer arg) } } else { - spf_record_process_addr (elt_data, cb, task); + spf_record_process_addr (addr, elt_data); } break; case SPF_RESOLVE_REDIRECT: if (elt_data->type == RDNS_REQUEST_TXT) { begin = elt_data->content.txt.data; - - if (!cb->in_include && cb->rec->addrs) { - g_list_free (cb->rec->addrs); - cb->rec->addrs = NULL; - } - start_spf_parse (cb->rec, begin, elt_data->ttl); - + ret = start_spf_parse (cb->rec, begin, 0); } break; case SPF_RESOLVE_INCLUDE: if (elt_data->type == RDNS_REQUEST_TXT) { begin = elt_data->content.txt.data; -#ifdef SPF_DEBUG - msg_info ("before include"); - dump_spf_record (cb->rec->addrs); -#endif - tmp = cb->rec->addrs; - cb->rec->addrs = NULL; - cb->rec->in_include = TRUE; ret = start_spf_parse (cb->rec, begin, 0); - cb->rec->in_include = FALSE; - -#ifdef SPF_DEBUG - msg_info ("after include"); - dump_spf_record (cb->rec->addrs); -#endif - - if (ret) { - /* Insert new list */ - cb->addr->is_list = TRUE; - cb->addr->data.list = cb->rec->addrs; - } - - cb->rec->addrs = tmp; } break; case SPF_RESOLVE_EXP: @@ -658,8 +520,7 @@ spf_record_dns_callback (struct rdns_reply *reply, gpointer arg) if (elt_data->type == RDNS_REQUEST_A || elt_data->type == RDNS_REQUEST_AAAA) { /* If specified address resolves, we can accept connection from every IP */ - cb->addr->data.normal.d.in4.s_addr = INADDR_NONE; - cb->addr->data.normal.mask = 0; + spf_record_addr_set (addr, TRUE); } break; } @@ -673,101 +534,202 @@ spf_record_dns_callback (struct rdns_reply *reply, gpointer arg) "<%s>: spf error for domain %s: cannot find MX record for %s", task->message_id, cb->rec->sender_domain, - cb->rec->cur_domain); - cb->addr->data.normal.d.in4.s_addr = INADDR_NONE; - cb->addr->data.normal.mask = 32; + cb->resolved->cur_domain); + spf_record_addr_set (addr, FALSE); } else { msg_info ( "<%s>: spf error for domain %s: cannot resolve MX record for %s", task->message_id, cb->rec->sender_domain, - cb->rec->cur_domain); - cb->addr->data.normal.d.in4.s_addr = INADDR_NONE; - cb->addr->data.normal.mask = 32; + cb->resolved->cur_domain); + spf_record_addr_set (addr, FALSE); } break; case SPF_RESOLVE_A: if (rdns_request_has_type (reply->request, RDNS_REQUEST_A)) { - cb->addr->data.normal.d.in4.s_addr = INADDR_NONE; - cb->addr->data.normal.mask = 32; + spf_record_addr_set (addr, FALSE); } break; -#ifdef HAVE_INET_PTON case SPF_RESOLVE_AAA: if (rdns_request_has_type (reply->request, RDNS_REQUEST_AAAA)) { - memset (&cb->addr->data.normal.d.in6, 0xff, - sizeof (struct in6_addr)); - cb->addr->data.normal.mask = 32; + spf_record_addr_set (addr, FALSE); } break; -#endif case SPF_RESOLVE_PTR: + spf_record_addr_set (addr, FALSE); break; case SPF_RESOLVE_REDIRECT: msg_info ( "<%s>: spf error for domain %s: cannot resolve TXT record for %s", task->message_id, cb->rec->sender_domain, - cb->rec->cur_domain); + cb->resolved->cur_domain); break; case SPF_RESOLVE_INCLUDE: msg_info ( "<%s>: spf error for domain %s: cannot resolve TXT record for %s", task->message_id, cb->rec->sender_domain, - cb->rec->cur_domain); + cb->resolved->cur_domain); break; case SPF_RESOLVE_EXP: break; case SPF_RESOLVE_EXISTS: - cb->addr->data.normal.d.in4.s_addr = INADDR_NONE; - cb->addr->data.normal.mask = 32; + spf_record_addr_set (addr, FALSE); break; } } - if (cb->rec->requests_inflight == 0) { - cb->rec->callback (cb->rec, cb->rec->task); - } + rspamd_spf_maybe_return (cb->rec); } -static gboolean -parse_spf_a (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +/* + * The syntax defined by the following BNF: + * [ ":" domain-spec ] [ dual-cidr-length ] + * ip4-cidr-length = "/" 1*DIGIT + * ip6-cidr-length = "/" 1*DIGIT + * dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] + */ +static const gchar * +parse_spf_domain_mask (struct spf_record *rec, struct spf_addr *addr, + gboolean allow_mask) { - struct spf_dns_cb *cb; - gchar *host = NULL; + struct spf_resolved_element *resolved; + struct rspamd_task *task = rec->task; + enum { + parse_spf_elt = 0, + parse_semicolon, + parse_domain, + parse_slash, + parse_ipv4_mask, + parse_second_slash, + parse_ipv6_mask, + skip_garbadge + } state = 0; + const gchar *p = addr->spf_string, *host, *c; + gchar *hostbuf; + gchar t; + guint16 cur_mask = 0; + + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); + host = resolved->cur_domain; - CHECK_REC (rec); + while (*p) { + t = *p; - /* - * a - * a/<prefix-length> - * a:<domain> - * a:<domain>/<prefix-length> - */ - if (begin == NULL) { - return FALSE; + switch (state) { + case parse_spf_elt: + if (t == ':') { + state = parse_semicolon; + } + else if (t == '/') { + /* No domain but mask */ + state = parse_slash; + } + p ++; + break; + case parse_semicolon: + if (t == '/') { + /* Empty domain, technically an error */ + state = parse_slash; + } + c = p; + state = parse_domain; + break; + case parse_domain: + if (t == '/') { + hostbuf = rspamd_mempool_alloc (task->task_pool, p - c + 1); + rspamd_strlcpy (hostbuf, c, p - c + 1); + host = hostbuf; + state = parse_slash; + } + p ++; + break; + case parse_slash: + c = p; + if (allow_mask) { + state = parse_ipv4_mask; + } + else { + state = skip_garbadge; + } + cur_mask = 0; + break; + case parse_ipv4_mask: + if (g_ascii_isdigit (t)) { + /* Ignore errors here */ + cur_mask = cur_mask * 10 + (t - '0'); + } + else if (t == '/') { + if (cur_mask <= 32) { + addr->m.dual.mask_v4 = cur_mask; + } + else { + msg_info ("bad ipv4 mask: %d", cur_mask); + } + state = parse_second_slash; + } + p ++; + break; + case parse_second_slash: + c = p; + state = parse_ipv6_mask; + cur_mask = 0; + break; + case parse_ipv6_mask: + if (g_ascii_isdigit (t)) { + /* Ignore errors here */ + cur_mask = cur_mask * 10 + (t - '0'); + } + p ++; + break; + case skip_garbadge: + p++; + break; + } } - if (*begin == '\0') { - /* Use current domain only */ - host = rec->cur_domain; - addr->data.normal.mask = 32; + + /* Process end states */ + if (state == parse_ipv4_mask) { + if (cur_mask <= 32) { + addr->m.dual.mask_v4 = cur_mask; + } + else { + msg_info ("bad ipv4 mask: %d", cur_mask); + } } - else if (*begin == ':') { - begin++; + else if (state == parse_ipv6_mask) { + if (cur_mask <= 128) { + addr->m.dual.mask_v6 = cur_mask; + } + else { + msg_info ("bad ipv6 mask: %d", cur_mask); + } } - else if (*begin != '/') { - /* Invalid A record */ - return FALSE; + else if (state == parse_domain && p - c > 0) { + hostbuf = rspamd_mempool_alloc (task->task_pool, p - c + 1); + rspamd_strlcpy (hostbuf, c, p - c + 1); + host = hostbuf; } - if (host == NULL) { - host = parse_spf_hostmask (task, begin, addr, rec); - } + return host; +} + +static gboolean +parse_spf_a (struct spf_record *rec, struct spf_addr *addr) +{ + struct spf_dns_cb *cb; + const gchar *host = NULL; + struct rspamd_task *task = rec->task; + struct spf_resolved_element *resolved; + + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); + CHECK_REC (rec); + + host = parse_spf_domain_mask (rec, addr, TRUE); if (host == NULL) { return FALSE; @@ -778,8 +740,9 @@ parse_spf_a (struct rspamd_task *task, cb->rec = rec; cb->addr = addr; cb->cur_action = SPF_RESOLVE_A; - cb->in_include = rec->in_include; + cb->resolved = resolved; msg_debug ("resolve a %s", host); + if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, RDNS_REQUEST_A, host)) { task->dns_requests++; @@ -792,43 +755,34 @@ parse_spf_a (struct rspamd_task *task, } static gboolean -parse_spf_ptr (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_ptr (struct spf_record *rec, struct spf_addr *addr) { struct spf_dns_cb *cb; gchar *host, *ptr; + struct rspamd_task *task = rec->task; + struct spf_resolved_element *resolved; + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); CHECK_REC (rec); - if (begin == NULL) { - return FALSE; - } - if (*begin == ':') { - begin++; - host = rspamd_mempool_strdup (task->task_pool, begin); - } - else if (*begin == '\0') { - host = NULL; - } - else { - return FALSE; - } + host = parse_spf_domain_mask (rec, addr, FALSE); rec->dns_requests++; cb = rspamd_mempool_alloc (task->task_pool, sizeof (struct spf_dns_cb)); cb->rec = rec; cb->addr = addr; cb->cur_action = SPF_RESOLVE_PTR; - cb->in_include = rec->in_include; + cb->resolved = resolved; cb->ptr_host = host; ptr = rdns_generate_ptr_from_str (rspamd_inet_address_to_string ( task->from_addr)); + if (ptr == NULL) { return FALSE; } + rspamd_mempool_add_destructor (task->task_pool, free, ptr); msg_debug ("resolve ptr %s for %s", ptr, host); if (make_dns_request (task->resolver, task->s, task->task_pool, @@ -840,38 +794,34 @@ parse_spf_ptr (struct rspamd_task *task, } return FALSE; - return TRUE; } static gboolean -parse_spf_mx (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_mx (struct spf_record *rec, struct spf_addr *addr) { struct spf_dns_cb *cb; gchar *host; + struct rspamd_task *task = rec->task; + struct spf_resolved_element *resolved; - CHECK_REC (rec); + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); - if (begin == NULL) { - return FALSE; - } - if (*begin == ':') { - begin++; - } + CHECK_REC (rec); - host = parse_spf_hostmask (task, begin, addr, rec); + host = parse_spf_domain_mask (rec, addr, TRUE); if (host == NULL) { return FALSE; } + rec->dns_requests++; cb = rspamd_mempool_alloc (task->task_pool, sizeof (struct spf_dns_cb)); cb->rec = rec; cb->addr = addr; cb->cur_action = SPF_RESOLVE_MX; - cb->in_include = rec->in_include; + cb->resolved = resolved; + msg_debug ("resolve mx for %s", host); if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, RDNS_REQUEST_MX, host)) { @@ -885,76 +835,141 @@ parse_spf_mx (struct rspamd_task *task, } static gboolean -parse_spf_all (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_all (struct spf_record *rec, struct spf_addr *addr) { /* All is 0/0 */ - memset (&addr->data.normal.d, 0, sizeof (addr->data.normal.d)); - if (rec->in_include) { - /* Ignore all record in include */ - addr->data.normal.mask = 32; - } - else { - addr->data.normal.mask = 0; - addr->data.normal.addr_any = TRUE; - } + memset (&addr->addr4, 0, sizeof (addr->addr4)); + memset (&addr->addr6, 0, sizeof (addr->addr6)); + /* Here we set all masks to 0 */ + addr->m.idx = 0; + addr->flags |= RSPAMD_SPF_FLAG_ANY; return TRUE; } static gboolean -parse_spf_ip4 (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_ip4 (struct spf_record *rec, struct spf_addr *addr) { /* ip4:addr[/mask] */ + const gchar *semicolon, *slash; + gsize len; + gchar ipbuf[INET_ADDRSTRLEN + 1]; + guint32 mask; CHECK_REC (rec); - return parse_spf_ipmask (begin, addr, rec); + + semicolon = strchr (addr->spf_string, ':'); + + if (semicolon == NULL) { + return FALSE; + } + + semicolon ++; + slash = strchr (semicolon, '/'); + + if (slash) { + len = slash - semicolon; + } + else { + len = strlen (semicolon); + } + + rspamd_strlcpy (ipbuf, semicolon, MIN (len, sizeof (ipbuf))); + + if (inet_pton (AF_INET, ipbuf, addr->addr4) != 1) { + return FALSE; + } + + if (slash) { + mask = strtoul (slash + 1, NULL, 10); + if (mask > 32) { + return FALSE; + } + addr->m.dual.mask_v4 = mask; + } + + addr->flags |= RSPAMD_SPF_FLAG_IPV4; + + return TRUE; } -#ifdef HAVE_INET_PTON static gboolean -parse_spf_ip6 (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_ip6 (struct spf_record *rec, struct spf_addr *addr) { /* ip6:addr[/mask] */ + const gchar *semicolon, *slash; + gsize len; + gchar ipbuf[INET6_ADDRSTRLEN + 1]; + guint32 mask; CHECK_REC (rec); - return parse_spf_ipmask (begin, addr, rec); + + semicolon = strchr (addr->spf_string, ':'); + + if (semicolon == NULL) { + return FALSE; + } + + semicolon ++; + slash = strchr (semicolon, '/'); + + if (slash) { + len = slash - semicolon; + } + else { + len = strlen (semicolon); + } + + rspamd_strlcpy (ipbuf, semicolon, MIN (len, sizeof (ipbuf))); + + if (inet_pton (AF_INET6, ipbuf, addr->addr4) != 1) { + return FALSE; + } + + if (slash) { + mask = strtoul (slash + 1, NULL, 10); + if (mask > 128) { + return FALSE; + } + addr->m.dual.mask_v6 = mask; + } + + addr->flags |= RSPAMD_SPF_FLAG_IPV6; + + return TRUE; } -#endif + static gboolean -parse_spf_include (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_include (struct spf_record *rec, struct spf_addr *addr) { struct spf_dns_cb *cb; gchar *domain; + struct rspamd_task *task = rec->task; + struct spf_resolved_element *resolved; + + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); CHECK_REC (rec); + domain = strchr (addr->spf_string, '='); - if (begin == NULL || *begin != ':') { + if (domain == NULL) { return FALSE; } - begin++; + + domain++; + rec->dns_requests++; cb = rspamd_mempool_alloc (task->task_pool, sizeof (struct spf_dns_cb)); cb->rec = rec; cb->addr = addr; cb->cur_action = SPF_RESOLVE_INCLUDE; - cb->in_include = rec->in_include; - addr->is_list = TRUE; - addr->data.list = NULL; - domain = rspamd_mempool_strdup (task->task_pool, begin); + /* Set reference */ + addr->flags |= RSPAMD_SPF_FLAG_REFRENCE; + addr->m.idx = rec->resolved->len; + rspamd_spf_new_addr_list (rec, domain); msg_debug ("resolve include %s", domain); if (make_dns_request (task->resolver, task->s, task->task_pool, @@ -970,10 +985,7 @@ parse_spf_include (struct rspamd_task *task, } static gboolean -parse_spf_exp (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_exp (struct spf_record *rec, struct spf_addr *addr) { CHECK_REC (rec); @@ -982,29 +994,51 @@ parse_spf_exp (struct rspamd_task *task, } static gboolean -parse_spf_redirect (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_redirect (struct spf_record *rec, struct spf_addr *addr) { struct spf_dns_cb *cb; - gchar *domain; + const gchar *domain; + struct spf_resolved_element *resolved; + struct spf_addr *cur; + struct rspamd_task *task = rec->task; + guint i; + + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); CHECK_REC (rec); - if (begin == NULL || *begin != '=') { + domain = strchr (addr->spf_string, '='); + + if (domain == NULL) { return FALSE; } - begin++; + + domain++; + rec->dns_requests++; + resolved->redirected = TRUE; + + /* Now clear all elements but this one */ + for (i = 0; i < resolved->elts->len; i ++) { + cur = g_ptr_array_index (resolved->elts, i); + + if (cur != addr) { + g_ptr_array_remove_index_fast (resolved->elts, i); + } + } cb = rspamd_mempool_alloc (task->task_pool, sizeof (struct spf_dns_cb)); + /* Set reference */ + addr->flags |= RSPAMD_SPF_FLAG_REFRENCE; + addr->m.idx = rec->resolved->len; + rspamd_spf_new_addr_list (rec, domain); + cb->rec = rec; cb->addr = addr; cb->cur_action = SPF_RESOLVE_REDIRECT; - cb->in_include = rec->in_include; - domain = rspamd_mempool_strdup (task->task_pool, begin); msg_debug ("resolve redirect %s", domain); + if (make_dns_request (task->resolver, task->s, task->task_pool, spf_record_dns_callback, (void *)cb, RDNS_REQUEST_TXT, domain)) { task->dns_requests++; @@ -1017,29 +1051,27 @@ parse_spf_redirect (struct rspamd_task *task, } static gboolean -parse_spf_exists (struct rspamd_task *task, - const gchar *begin, - struct spf_record *rec, - struct spf_addr *addr) +parse_spf_exists (struct spf_record *rec, struct spf_addr *addr) { struct spf_dns_cb *cb; - gchar *host; + const gchar *host; + struct rspamd_task *task = rec->task; CHECK_REC (rec); - if (begin == NULL || *begin != ':') { + host = strchr (addr->spf_string, ':'); + if (host == NULL) { + msg_info ("bad SPF exist record: %s", addr->spf_string); return FALSE; } - begin++; + + host ++; rec->dns_requests++; - addr->data.normal.mask = 32; cb = rspamd_mempool_alloc (task->task_pool, sizeof (struct spf_dns_cb)); cb->rec = rec; cb->addr = addr; cb->cur_action = SPF_RESOLVE_EXISTS; - cb->in_include = rec->in_include; - host = rspamd_mempool_strdup (task->task_pool, begin); msg_debug ("resolve exists %s", host); if (make_dns_request (task->resolver, task->s, task->task_pool, @@ -1081,21 +1113,27 @@ reverse_spf_ip (gchar *ip, gint len) } memcpy (p - 1, c - t, t + 1); - memcpy (ip, ipbuf, len); } -static gchar * -expand_spf_macro (struct rspamd_task *task, struct spf_record *rec, - gchar *begin) +static const gchar * +expand_spf_macro (struct spf_record *rec, + const gchar *begin) { - gchar *p, *c, *new, *tmp; + const gchar *p, *c; + gchar *new, *tmp; gint len = 0, slen = 0, state = 0; -#ifdef HAVE_INET_PTON gchar ip_buf[INET6_ADDRSTRLEN]; -#endif gboolean need_expand = FALSE; + struct rspamd_task *task; + struct spf_resolved_element *resolved; + + g_assert (rec != NULL); + g_assert (begin != NULL); + task = rec->task; + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); p = begin; /* Calculate length */ while (*p) { @@ -1140,11 +1178,7 @@ expand_spf_macro (struct rspamd_task *task, struct spf_record *rec, /* Read macro name */ switch (g_ascii_tolower (*p)) { case 'i': -#ifdef HAVE_INET_PTON len += INET6_ADDRSTRLEN - 1; -#else - len += INET_ADDRSTRLEN - 1; -#endif break; case 's': len += strlen (rec->sender); @@ -1156,7 +1190,7 @@ expand_spf_macro (struct rspamd_task *task, struct spf_record *rec, len += strlen (rec->sender_domain); break; case 'd': - len += strlen (rec->cur_domain); + len += strlen (resolved->cur_domain); break; case 'v': len += sizeof ("in-addr") - 1; @@ -1281,8 +1315,8 @@ expand_spf_macro (struct rspamd_task *task, struct spf_record *rec, c += len; break; case 'd': - len = strlen (rec->cur_domain); - memcpy (c, rec->cur_domain, len); + len = strlen (resolved->cur_domain); + memcpy (c, resolved->cur_domain, len); c += len; break; case 'v': @@ -1344,174 +1378,138 @@ expand_spf_macro (struct rspamd_task *task, struct spf_record *rec, } -#define NEW_ADDR(x) do { \ - (x) = rspamd_mempool_alloc (task->task_pool, sizeof (struct spf_addr)); \ - (x)->mech = check_spf_mech (rec->cur_elt, &need_shift); \ - (x)->spf_string = rspamd_mempool_strdup (task->task_pool, begin); \ - memset (&(x)->data.normal, 0, sizeof ((x)->data.normal)); \ - (x)->data.normal.mask = 32; \ - (x)->is_list = FALSE; \ -} while (0); - /* Read current element and try to parse record */ static gboolean -parse_spf_record (struct rspamd_task *task, struct spf_record *rec) +parse_spf_record (struct spf_record *rec, const gchar *elt) { - struct spf_addr *new = NULL; - gboolean need_shift, res = FALSE; - gchar *begin; + struct spf_addr *addr = NULL; + gboolean res = FALSE; + const gchar *begin; + struct rspamd_task *task; + struct spf_resolved_element *resolved; + gchar t; - rec->cur_elt = rec->elts[rec->elt_num]; - if (rec->cur_elt == NULL) { - return FALSE; - } - else if (*rec->cur_elt == '\0') { - /* Silently skip empty elements */ - rec->elt_num++; + resolved = &g_array_index (rec->resolved, struct spf_resolved_element, + rec->resolved->len - 1); + + g_assert (elt != NULL); + g_assert (rec != NULL); + + if (*elt == '\0' || resolved->redirected) { return TRUE; } - else { - begin = expand_spf_macro (task, rec, rec->cur_elt); - if (*begin == '?' || *begin == '+' || *begin == '-' || *begin == '~') { - begin++; - } + task = rec->task; + begin = expand_spf_macro (rec, elt); + addr = rspamd_spf_new_addr (rec, begin); + g_assert (addr != NULL); + t = g_ascii_tolower (addr->spf_string[0]); + begin = addr->spf_string; - /* Now check what we have */ - switch (g_ascii_tolower (*begin)) { - case 'a': - /* all or a */ - if (g_ascii_strncasecmp (begin, SPF_ALL, + /* Now check what we have */ + switch (t) { + case 'a': + /* all or a */ + if (g_ascii_strncasecmp (begin, SPF_ALL, sizeof (SPF_ALL) - 1) == 0) { - NEW_ADDR (new); - begin += sizeof (SPF_ALL) - 1; - res = parse_spf_all (task, begin, rec, new); - } - else if (g_ascii_strncasecmp (begin, SPF_A, + res = parse_spf_all (rec, addr); + } + else if (g_ascii_strncasecmp (begin, SPF_A, sizeof (SPF_A) - 1) == 0) { - NEW_ADDR (new); - begin += sizeof (SPF_A) - 1; - res = parse_spf_a (task, begin, rec, new); - } - else { - msg_info ("<%s>: spf error for domain %s: bad spf command %s", + res = parse_spf_a (rec, addr); + } + else { + msg_info ("<%s>: spf error for domain %s: bad spf command %s", task->message_id, rec->sender_domain, begin); - } - break; - case 'i': - /* include or ip4 */ - if (g_ascii_strncasecmp (begin, SPF_IP4, + } + break; + case 'i': + /* include or ip4 */ + if (g_ascii_strncasecmp (begin, SPF_IP4, sizeof (SPF_IP4) - 1) == 0) { - NEW_ADDR (new); - begin += sizeof (SPF_IP4) - 1; - res = parse_spf_ip4 (task, begin, rec, new); - } - else if (g_ascii_strncasecmp (begin, SPF_INCLUDE, + res = parse_spf_ip4 (rec, addr); + } + else if (g_ascii_strncasecmp (begin, SPF_INCLUDE, sizeof (SPF_INCLUDE) - 1) == 0) { - NEW_ADDR (new); - begin += sizeof (SPF_INCLUDE) - 1; - res = parse_spf_include (task, begin, rec, new); - } - else if (g_ascii_strncasecmp (begin, SPF_IP6, sizeof (SPF_IP6) - + res = parse_spf_include (rec, addr); + } + else if (g_ascii_strncasecmp (begin, SPF_IP6, sizeof (SPF_IP6) - 1) == 0) { -#ifdef HAVE_INET_PTON - NEW_ADDR (new); - begin += sizeof (SPF_IP6) - 1; - res = parse_spf_ip6 (task, begin, rec, new); -#else - msg_info ( - "ignoring ip6 spf command as IPv6 is not supported: %s", - begin); - new = NULL; - res = TRUE; - begin += sizeof (SPF_IP6) - 1; -#endif - } - else { - msg_info ("<%s>: spf error for domain %s: bad spf command %s", + res = parse_spf_ip6 (rec, addr); + } + else { + msg_info ("<%s>: spf error for domain %s: bad spf command %s", task->message_id, rec->sender_domain, begin); - } - break; - case 'm': - /* mx */ - if (g_ascii_strncasecmp (begin, SPF_MX, sizeof (SPF_MX) - 1) == 0) { - NEW_ADDR (new); - begin += sizeof (SPF_MX) - 1; - res = parse_spf_mx (task, begin, rec, new); - } - else { - msg_info ("<%s>: spf error for domain %s: bad spf command %s", + } + break; + case 'm': + /* mx */ + if (g_ascii_strncasecmp (begin, SPF_MX, sizeof (SPF_MX) - 1) == 0) { + res = parse_spf_mx (rec, addr); + } + else { + msg_info ("<%s>: spf error for domain %s: bad spf command %s", task->message_id, rec->sender_domain, begin); - } - break; - case 'p': - /* ptr */ - if (g_ascii_strncasecmp (begin, SPF_PTR, + } + break; + case 'p': + /* ptr */ + if (g_ascii_strncasecmp (begin, SPF_PTR, sizeof (SPF_PTR) - 1) == 0) { - NEW_ADDR (new); - begin += sizeof (SPF_PTR) - 1; - res = parse_spf_ptr (task, begin, rec, new); - } - else { - msg_info ("<%s>: spf error for domain %s: bad spf command %s", + res = parse_spf_ptr (rec, addr); + } + else { + msg_info ("<%s>: spf error for domain %s: bad spf command %s", task->message_id, rec->sender_domain, begin); - } - break; - case 'e': - /* exp or exists */ - if (g_ascii_strncasecmp (begin, SPF_EXP, + } + break; + case 'e': + /* exp or exists */ + if (g_ascii_strncasecmp (begin, SPF_EXP, sizeof (SPF_EXP) - 1) == 0) { - begin += sizeof (SPF_EXP) - 1; - res = parse_spf_exp (task, begin, rec, NULL); - } - else if (g_ascii_strncasecmp (begin, SPF_EXISTS, + res = parse_spf_exp (rec, addr); + } + else if (g_ascii_strncasecmp (begin, SPF_EXISTS, sizeof (SPF_EXISTS) - 1) == 0) { - NEW_ADDR (new); - begin += sizeof (SPF_EXISTS) - 1; - res = parse_spf_exists (task, begin, rec, new); - } - else { - msg_info ("<%s>: spf error for domain %s: bad spf command %s", + res = parse_spf_exists (rec, addr); + } + else { + msg_info ("<%s>: spf error for domain %s: bad spf command %s", task->message_id, rec->sender_domain, begin); - } - break; - case 'r': - /* redirect */ - if (g_ascii_strncasecmp (begin, SPF_REDIRECT, + } + break; + case 'r': + /* redirect */ + if (g_ascii_strncasecmp (begin, SPF_REDIRECT, sizeof (SPF_REDIRECT) - 1) == 0) { - begin += sizeof (SPF_REDIRECT) - 1; - res = parse_spf_redirect (task, begin, rec, NULL); - } - else { - msg_info ("<%s>: spf error for domain %s: bad spf command %s", - task->message_id, rec->sender_domain, begin); - } - break; - case 'v': - if (g_ascii_strncasecmp (begin, "v=spf", - sizeof ("v=spf") - 1) == 0) { - /* Skip this element till the end of record */ - while (*begin && !g_ascii_isspace (*begin)) { - begin++; - } - } - break; - default: + res = parse_spf_redirect (rec, addr); + } + else { msg_info ("<%s>: spf error for domain %s: bad spf command %s", - task->message_id, rec->sender_domain, begin); - break; + task->message_id, rec->sender_domain, begin); } - if (res) { - if (new != NULL) { - rec->addrs = g_list_prepend (rec->addrs, new); + break; + case 'v': + if (g_ascii_strncasecmp (begin, "v=spf", + sizeof ("v=spf") - 1) == 0) { + /* Skip this element till the end of record */ + while (*begin && !g_ascii_isspace (*begin)) { + begin++; } - rec->elt_num++; } + break; + default: + msg_info ("<%s>: spf error for domain %s: bad spf command %s", + task->message_id, rec->sender_domain, begin); + break; + } + + if (res) { + addr->flags |= RSPAMD_SPF_FLAG_VALID; } return res; } -#undef NEW_ADDR static void parse_spf_scopes (struct spf_record *rec, gchar **begin) @@ -1538,31 +1536,21 @@ parse_spf_scopes (struct spf_record *rec, gchar **begin) } static gboolean -start_spf_parse (struct spf_record *rec, gchar *begin, guint ttl) +start_spf_parse (struct spf_record *rec, gchar *begin) { + gchar **elts, **cur_elt; + /* Skip spaces */ while (g_ascii_isspace (*begin)) { begin++; } - if (g_ascii_strncasecmp (begin, SPF_VER1_STR, - sizeof (SPF_VER1_STR) - 1) == 0) { + if (g_ascii_strncasecmp (begin, SPF_VER1_STR, sizeof (SPF_VER1_STR) - 1) == 0) { begin += sizeof (SPF_VER1_STR) - 1; + while (g_ascii_isspace (*begin) && *begin) { begin++; } - rec->elts = g_strsplit_set (begin, " ", 0); - rec->elt_num = 0; - if (rec->elts) { - rspamd_mempool_add_destructor (rec->task->task_pool, - (rspamd_mempool_destruct_t)g_strfreev, rec->elts); - rec->cur_elt = rec->elts[0]; - while (parse_spf_record (rec->task, rec)) ; - if (ttl != 0) { - rec->ttl = ttl; - } - return TRUE; - } } else if (g_ascii_strncasecmp (begin, SPF_VER2_STR, sizeof (SPF_VER2_STR) - 1) == 0) { @@ -1577,21 +1565,6 @@ start_spf_parse (struct spf_record *rec, gchar *begin, guint ttl) parse_spf_scopes (rec, &begin); } /* Now common spf record */ - while (g_ascii_isspace (*begin) && *begin) { - begin++; - } - rec->elts = g_strsplit_set (begin, " ", 0); - rec->elt_num = 0; - if (rec->elts) { - rspamd_mempool_add_destructor (rec->task->task_pool, - (rspamd_mempool_destruct_t)g_strfreev, rec->elts); - rec->cur_elt = rec->elts[0]; - while (parse_spf_record (rec->task, rec)) ; - if (ttl != 0) { - rec->ttl = ttl; - } - } - return TRUE; } else { msg_debug ("<%s>: spf error for domain %s: bad spf record version: %*s", @@ -1599,8 +1572,29 @@ start_spf_parse (struct spf_record *rec, gchar *begin, guint ttl) rec->sender_domain, sizeof (SPF_VER1_STR) - 1, begin); + return FALSE; } - return FALSE; + + while (g_ascii_isspace (*begin) && *begin) { + begin++; + } + + elts = g_strsplit_set (begin, " ", 0); + + if (elts) { + cur_elt = elts; + + while (*cur_elt) { + parse_spf_record (rec, *cur_elt); + cur_elt ++; + } + + g_strfreev (elts); + } + + rspamd_spf_maybe_return (rec); + + return TRUE; } static void @@ -1611,23 +1605,25 @@ spf_dns_callback (struct rdns_reply *reply, gpointer arg) rec->requests_inflight--; if (reply->code == RDNS_RC_NOERROR) { - LL_FOREACH (reply->entries, elt) - { - if (start_spf_parse (rec, elt->content.txt.data, elt->ttl)) { + if (rec->resolved->len == 1) { + /* Top level resolved element */ + rec->ttl = reply->entries->ttl; + } + + LL_FOREACH (reply->entries, elt) { + if (start_spf_parse (rec, elt->content.txt.data)) { break; } } } - if (rec->requests_inflight == 0) { - rec->callback (rec, rec->task); - } + rspamd_spf_maybe_return (rec); } -gchar * +const gchar * get_spf_domain (struct rspamd_task *task) { - gchar *domain, *res = NULL; + const gchar *domain, *res = NULL; const gchar *sender; sender = rspamd_task_get_sender (task); @@ -1635,7 +1631,7 @@ get_spf_domain (struct rspamd_task *task) if (sender != NULL) { domain = strchr (sender, '@'); if (domain) { - res = rspamd_mempool_strdup (task->task_pool, domain + 1); + res = domain + 1; } } @@ -1655,6 +1651,8 @@ resolve_spf (struct rspamd_task *task, spf_cb_t callback) rec->task = task; rec->callback = callback; + rspamd_spf_new_addr_list (rec, rec->sender_domain); + /* Add destructor */ rspamd_mempool_add_destructor (task->task_pool, (rspamd_mempool_destruct_t)spf_record_destructor, @@ -1667,36 +1665,38 @@ resolve_spf (struct rspamd_task *task, spf_cb_t callback) rec->local_part = rspamd_mempool_alloc (task->task_pool, domain - sender); rspamd_strlcpy (rec->local_part, sender, domain - sender); - rec->cur_domain = rspamd_mempool_strdup (task->task_pool, domain + 1); - rec->sender_domain = rec->cur_domain; - - if (make_dns_request (task->resolver, task->s, task->task_pool, - spf_dns_callback, - (void *)rec, RDNS_REQUEST_TXT, rec->cur_domain)) { - task->dns_requests++; - rec->requests_inflight++; - return TRUE; - } + rec->sender_domain = domain + 1; } else if (task->helo != NULL && strchr (task->helo, '.') != NULL) { - /* For notifies we can check HELO identity and check SPF accrodingly */ + /* For notifies we can check HELO identity and check SPF accordingly */ /* XXX: very poor check */ rec->local_part = rspamd_mempool_strdup (task->task_pool, "postmaster"); - rec->cur_domain = task->helo; rec->sender_domain = task->helo; + } + else { + return FALSE; + } - if (make_dns_request (task->resolver, task->s, task->task_pool, - spf_dns_callback, - (void *)rec, RDNS_REQUEST_TXT, rec->cur_domain)) { - task->dns_requests++; - rec->requests_inflight++; - return TRUE; - } + if (make_dns_request (task->resolver, task->s, task->task_pool, + spf_dns_callback, + (void *)rec, RDNS_REQUEST_TXT, rec->sender_domain)) { + task->dns_requests++; + rec->requests_inflight++; + return TRUE; } return FALSE; } -/* - * vi:ts=4 - */ +struct spf_resolved * +spf_record_ref (struct spf_resolved *rec) +{ + REF_RETAIN (rec); + return rec; +} + +void +spf_record_unref (struct spf_resolved *rec) +{ + REF_RELEASE (rec); +} diff --git a/src/libserver/spf.h b/src/libserver/spf.h index 616595c35..473eff086 100644 --- a/src/libserver/spf.h +++ b/src/libserver/spf.h @@ -2,11 +2,12 @@ #define RSPAMD_SPF_H #include "config.h" +#include "ref.h" struct rspamd_task; -struct spf_record; +struct spf_resolved; -typedef void (*spf_cb_t)(struct spf_record *record, struct rspamd_task *task); +typedef void (*spf_cb_t)(struct spf_resolved *record, struct rspamd_task *task); typedef enum spf_mech_e { SPF_FAIL, @@ -26,47 +27,33 @@ typedef enum spf_action_e { SPF_RESOLVE_EXP } spf_action_t; +#define RSPAMD_SPF_FLAG_IPV6 (1 << 0) +#define RSPAMD_SPF_FLAG_IPV4 (1 << 1) +#define RSPAMD_SPF_FLAG_ANY (1 << 2) +#define RSPAMD_SPF_FLAG_PARSED (1 << 3) +#define RSPAMD_SPF_FLAG_VALID (1 << 4) +#define RSPAMD_SPF_FLAG_REFRENCE (1 << 5) + struct spf_addr { + guchar addr6[sizeof (struct in6_addr)]; + guchar addr4[sizeof (struct in_addr)]; union { struct { - union { - struct in_addr in4; -#ifdef HAVE_INET_PTON - struct in6_addr in6; -#endif - } d; - guint32 mask; - gboolean ipv6; - gboolean parsed; - gboolean addr_any; - } normal; - GList *list; - } data; - gboolean is_list; + guint16 mask_v4; + guint16 mask_v6; + } dual; + guint32 idx; + } m; + guint flags; spf_mech_t mech; gchar *spf_string; }; -struct spf_record { - gchar **elts; - - gchar *cur_elt; - gint elt_num; - gint nested; - gint dns_requests; - gint requests_inflight; - +struct spf_resolved { + gchar *domain; guint ttl; - - GList *addrs; - gchar *cur_domain; - const gchar *sender; - gchar *sender_domain; - gchar *local_part; - struct rspamd_task *task; - spf_cb_t callback; - - gboolean in_include; + GArray *elts; /* Flat list of struct spf_addr */ + ref_entry_t ref; /* Refcounting */ }; @@ -78,7 +65,17 @@ gboolean resolve_spf (struct rspamd_task *task, spf_cb_t callback); /* * Get a domain for spf for specified task */ -gchar * get_spf_domain (struct rspamd_task *task); +const gchar * get_spf_domain (struct rspamd_task *task); +/* + * Increase refcount + */ +struct spf_resolved * spf_record_ref (struct spf_resolved *rec); + +/* + * Decrease refcount + */ +void spf_record_unref (struct spf_resolved *rec); + #endif diff --git a/src/plugins/spf.c b/src/plugins/spf.c index d74f481c2..a2869d04a 100644 --- a/src/plugins/spf.c +++ b/src/plugins/spf.c @@ -166,8 +166,8 @@ spf_module_config (struct rspamd_config *cfg) spf_module_ctx->spf_hash = rspamd_lru_hash_new ( cache_size, cache_expire, - g_free, - spf_record_destroy); + NULL, + (GDestroyNotify)spf_record_unref); return res; } @@ -188,13 +188,10 @@ static gboolean spf_check_element (struct spf_addr *addr, struct rspamd_task *task) { gboolean res = FALSE; - guint8 *s, *d, t, *buf; + const guint8 *s, *d; gchar *spf_result; - gint af; + guint af, mask, bmask, addrlen; const gchar *spf_message, *spf_symbol; - guint nbits, addrlen; - struct in_addr in4s; - struct in6_addr in6s; GList *opts = NULL; if (task->from_addr == NULL) { @@ -203,48 +200,42 @@ spf_check_element (struct spf_addr *addr, struct rspamd_task *task) af = rspamd_inet_address_get_af (task->from_addr); /* Basic comparing algorithm */ - if ((addr->data.normal.ipv6 && af == AF_INET6) || - (!addr->data.normal.ipv6 && af == AF_INET)) { + if (((addr->flags & RSPAMD_SPF_FLAG_IPV6) && af == AF_INET6) || + ((addr->flags & RSPAMD_SPF_FLAG_IPV4) && af == AF_INET)) { d = rspamd_inet_address_get_radix_key (task->from_addr, &addrlen); - buf = g_alloca (addrlen); - memcpy (buf, d, addrlen); - d = buf; - - if (addr->data.normal.ipv6) { - memcpy (&in6s, &addr->data.normal.d.in6, - sizeof (struct in6_addr)); - s = (guint8 *)&in6s; + + if (af == AF_INET6) { + s = (const guint8 *)addr->addr6; + mask = addr->m.dual.mask_v6; } else { - memcpy (&in4s, &addr->data.normal.d.in4, - sizeof (struct in_addr)); - s = (guint8 *)&in4s; + s = (const guint8 *)addr->addr4; + mask = addr->m.dual.mask_v4; } - /* Move pointers to the less significant byte */ - t = 0x1; - s += addrlen - 1; - d += addrlen - 1; - /* TODO: improve this cycle by masking by words */ - for (nbits = 0; - nbits < addrlen * CHAR_BIT - addr->data.normal.mask; - nbits++) { - /* Skip bits from the beginning as we know that data is in network byte order */ - if (nbits != 0 && nbits % 8 == 0) { - /* Move pointer to the next byte */ - s--; - d--; - t = 0x1; + /* Compare the first bytes */ + bmask = mask / CHAR_BIT; + if (bmask > addrlen) { + msg_info ("bad mask length: %d", mask); + } + else if (memcmp (s, d, bmask) == 0) { + + if (bmask * CHAR_BIT != mask) { + /* Compare the remaining bits */ + s += bmask; + d += bmask; + mask = (0xff << (CHAR_BIT - (mask - bmask * 8))) & 0xff; + if ((*s & mask) == (*d & mask)) { + res = TRUE; + } + } + else { + res = TRUE; } - *s |= t; - *d |= t; - t <<= 1; } - - res = memcmp (d, s, addrlen); } else { - if (addr->data.normal.addr_any) { + if (addr->flags & RSPAMD_SPF_FLAG_ANY) { res = TRUE; } else { @@ -284,49 +275,39 @@ spf_check_element (struct spf_addr *addr, struct rspamd_task *task) return FALSE; } -static gboolean -spf_check_list (GList *list, struct rspamd_task *task) +static void +spf_check_list (struct spf_resolved *rec, struct rspamd_task *task) { - GList *cur; + guint i; struct spf_addr *addr; - cur = list; + for (i = 0; i < rec->elts->len; i ++) { + addr = &g_array_index (rec->elts, struct spf_addr, i); - while (cur) { - addr = cur->data; - if (addr->is_list) { - /* Recursive call */ - if (spf_check_list (addr->data.list, task)) { - return TRUE; - } - } - else { - if (spf_check_element (addr, task)) { - return TRUE; - } + if (spf_check_element (addr, task)) { + break; } - cur = g_list_next (cur); } - - return FALSE; } static void -spf_plugin_callback (struct spf_record *record, struct rspamd_task *task) +spf_plugin_callback (struct spf_resolved *record, struct rspamd_task *task) { - GList *l; + struct spf_resolved *l; - if (record && record->addrs && record->sender_domain) { + if (record && record->elts->len > 0 && record->domain) { if ((l = rspamd_lru_hash_lookup (spf_module_ctx->spf_hash, - record->sender_domain, task->tv.tv_sec)) == NULL) { - l = spf_record_copy (record->addrs); + record->domain, task->tv.tv_sec)) == NULL) { + rspamd_lru_hash_insert (spf_module_ctx->spf_hash, - g_strdup (record->sender_domain), - l, task->tv.tv_sec, record->ttl); + record->domain, spf_record_ref (record), + task->tv.tv_sec, record->ttl); } + spf_record_ref (l); spf_check_list (l, task); + spf_record_unref (l); } } @@ -352,62 +333,3 @@ spf_symbol_callback (struct rspamd_task *task, void *unused) } } } - -/* - * Make a deep copy of list, note copy is REVERSED - */ -static GList * -spf_record_copy (GList *addrs) -{ - GList *cur, *newl = NULL; - struct spf_addr *addr, *newa; - - cur = addrs; - - while (cur) { - addr = cur->data; - newa = g_malloc (sizeof (struct spf_addr)); - memcpy (newa, addr, sizeof (struct spf_addr)); - if (addr->is_list) { - /* Recursive call */ - newa->data.list = spf_record_copy (addr->data.list); - } - else { - if (addr->spf_string) { - newa->spf_string = g_strdup (addr->spf_string); - } - } - newl = g_list_prepend (newl, newa); - cur = g_list_next (cur); - } - - return newl; -} - -/* - * Destroy allocated spf list - */ - - -static void -spf_record_destroy (gpointer list) -{ - GList *cur = list; - struct spf_addr *addr; - - while (cur) { - addr = cur->data; - if (addr->is_list) { - spf_record_destroy (addr->data.list); - } - else { - if (addr->spf_string) { - g_free (addr->spf_string); - } - } - g_free (addr); - cur = g_list_next (cur); - } - - g_list_free (list); -} |