summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2015-03-18 17:24:08 +0000
committerVsevolod Stakhov <vsevolod@highsecure.ru>2015-03-18 17:24:08 +0000
commit59cc1c9179db300edcfe5baf86755f129b6945bb (patch)
tree945dd74fba52b477beb89293fe8f98d0f8080ec0
parent19d825e68e0da0e1ef9100d1c7162ba8c4d4824e (diff)
parentcc27a6dcda6076d6db3a2bba740832c49dc11c2d (diff)
downloadrspamd-59cc1c9179db300edcfe5baf86755f129b6945bb.tar.gz
rspamd-59cc1c9179db300edcfe5baf86755f129b6945bb.zip
Merge branch 'spf-rework'
-rw-r--r--src/libserver/spf.c1414
-rw-r--r--src/libserver/spf.h69
-rw-r--r--src/plugins/spf.c172
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);
-}