--- /dev/null
+/*
+ * Copyright (c) 2009, Rambler media
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Rambler media ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL Rambler BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/***MODULE:spf
+ * rspamd module that checks spf records of incoming email
+ */
+
+#include "../config.h"
+#include "../main.h"
+#include "../message.h"
+#include "../modules.h"
+#include "../cfg_file.h"
+#include "../expressions.h"
+#include "../util.h"
+#include "../view.h"
+#include "../map.h"
+#include "../spf.h"
+
+#define DEFAULT_SYMBOL_FAIL "R_SPF_FAIL"
+#define DEFAULT_SYMBOL_SOFTFAIL "R_SPF_SOFTFAIL"
+#define DEFAULT_SYMBOL_ALLOW "R_SPF_ALLOW"
+
+struct spf_ctx {
+ int (*filter) (struct worker_task * task);
+ char *metric;
+ char *symbol_fail;
+ char *symbol_softfail;
+ char *symbol_allow;
+
+ memory_pool_t *spf_pool;
+};
+
+static struct spf_ctx *spf_module_ctx = NULL;
+
+static void spf_symbol_callback (struct worker_task *task, void *unused);
+
+int
+spf_module_init (struct config_file *cfg, struct module_ctx **ctx)
+{
+ spf_module_ctx = g_malloc (sizeof (struct spf_ctx));
+
+ spf_module_ctx->spf_pool = memory_pool_new (memory_pool_get_size ());
+
+ *ctx = (struct module_ctx *)spf_module_ctx;
+
+ return 0;
+}
+
+
+int
+spf_module_config (struct config_file *cfg)
+{
+ char *value;
+ int res = TRUE;
+ struct metric *metric;
+ double *w;
+
+ if ((value = get_module_opt (cfg, "spf", "metric")) != NULL) {
+ spf_module_ctx->metric = memory_pool_strdup (spf_module_ctx->spf_pool, value);
+ g_free (value);
+ }
+ else {
+ spf_module_ctx->metric = DEFAULT_METRIC;
+ }
+ if ((value = get_module_opt (cfg, "spf", "symbol_fail")) != NULL) {
+ spf_module_ctx->symbol_fail = memory_pool_strdup (spf_module_ctx->spf_pool, value);
+ g_free (value);
+ }
+ else {
+ spf_module_ctx->symbol_fail = DEFAULT_SYMBOL_FAIL;
+ }
+ if ((value = get_module_opt (cfg, "spf", "symbol_softfail")) != NULL) {
+ spf_module_ctx->symbol_softfail = memory_pool_strdup (spf_module_ctx->spf_pool, value);
+ g_free (value);
+ }
+ else {
+ spf_module_ctx->symbol_softfail = DEFAULT_SYMBOL_SOFTFAIL;
+ }
+ if ((value = get_module_opt (cfg, "spf", "symbol_allow")) != NULL) {
+ spf_module_ctx->symbol_allow = memory_pool_strdup (spf_module_ctx->spf_pool, value);
+ g_free (value);
+ }
+ else {
+ spf_module_ctx->symbol_allow = DEFAULT_SYMBOL_ALLOW;
+ }
+
+ metric = g_hash_table_lookup (cfg->metrics, spf_module_ctx->metric);
+ if (metric == NULL) {
+ msg_err ("spf_module_config: cannot find metric definition %s", spf_module_ctx->metric);
+ return FALSE;
+ }
+
+ /* Search in factors hash table */
+ w = g_hash_table_lookup (cfg->factors, spf_module_ctx->symbol_fail);
+ if (w == NULL) {
+ register_symbol (&metric->cache, spf_module_ctx->symbol_fail, 1, spf_symbol_callback, NULL);
+ }
+ else {
+ register_symbol (&metric->cache, spf_module_ctx->symbol_fail, *w, spf_symbol_callback, NULL);
+ }
+
+ return res;
+}
+
+int
+spf_module_reconfig (struct config_file *cfg)
+{
+ memory_pool_delete (spf_module_ctx->spf_pool);
+ spf_module_ctx->spf_pool = memory_pool_new (memory_pool_get_size ());
+
+ return spf_module_config (cfg);
+}
+
+static void
+spf_plugin_callback (struct spf_record *record, struct worker_task *task)
+{
+ if (task->save.saved == 0) {
+ /* Call other filters */
+ task->save.saved = 1;
+ process_filters (task);
+ }
+}
+
+
+static void
+spf_symbol_callback (struct worker_task *task, void *unused)
+{
+ if (!resolve_spf (task, spf_plugin_callback)) {
+ msg_info ("spf_symbol_callback: cannot make spf request for [%s]", task->message_id);
+ }
+}
#include "message.h"
#include "filter.h"
-#define SPF_VER_STR "spf=v1"
+#define SPF_VER_STR "v=spf1"
#define SPF_ALL "all"
#define SPF_A "a"
#define SPF_IP4 "ip4"
return FALSE; \
} \
} while (0) \
-
+
+static gboolean parse_spf_record (struct worker_task *task, struct spf_record *rec);
+static void start_spf_parse (struct spf_record *rec, char *begin);
/* Determine spf mech */
static spf_mech_t
}
+static char *
+parse_spf_hostmask (struct worker_task *task, const char *begin, struct spf_addr *addr, struct spf_record *rec)
+{
+ char *host = NULL, *p, mask_buf[3];
+ int 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 */
+ g_strlcpy (mask_buf, p + 1, sizeof (mask_buf));
+ addr->mask = mask_buf[0] * 10 + mask_buf[1];
+ if (addr->mask > 32) {
+ return FALSE;
+ }
+ if (host == NULL) {
+ hostlen = p - begin;
+ host = memory_pool_alloc (task->task_pool, hostlen);
+ g_strlcpy (host, begin, hostlen);
+ }
+ }
+ else {
+ addr->mask = 32;
+ if (host == NULL) {
+ g_strlcpy (host, begin, strlen (begin));
+ }
+ }
+
+ return host;
+}
+
static void
spf_record_dns_callback (int result, char type, int count, int ttl, void *addresses, void *data)
{
struct spf_dns_cb *cb = data;
char *begin;
+ struct evdns_mx *mx;
if (result == DNS_ERR_NONE) {
if (addresses != NULL) {
/* Add all logic for all DNS states here */
switch (cb->cur_action) {
case SPF_RESOLVE_MX:
+ if (type == DNS_MX) {
+ mx = (struct evdns_mx *)addresses;
+ /* Now resolve A record for this MX */
+ if (evdns_resolve_ipv4 (mx->host, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+ return;
+ }
+ }
+ else if (type == DNS_IPv4_A) {
+ /* XXX: process only one record */
+ cb->addr->addr = *((uint32_t *)addresses);
+ }
break;
case SPF_RESOLVE_A:
if (type == DNS_IPv4_A) {
case SPF_RESOLVE_PTR:
break;
case SPF_RESOLVE_REDIRECT:
+ if (type == DNS_TXT) {
+ if (addresses != NULL) {
+ begin = *(char **)addresses;
+ start_spf_parse (cb->rec, begin);
+ }
+ }
break;
case SPF_RESOLVE_INCLUDE:
+ if (type == DNS_TXT) {
+ if (addresses != NULL) {
+ begin = *(char **)addresses;
+ start_spf_parse (cb->rec, begin);
+ }
+ }
break;
case SPF_RESOLVE_EXP:
break;
+ case SPF_RESOLVE_EXISTS:
+ if (type == DNS_IPv4_A) {
+ /* If specified address resolves, we can accept connection from every IP */
+ cb->addr->addr = INADDR_ANY;
+ }
+ break;
}
}
}
parse_spf_a (struct worker_task *task, const char *begin, struct spf_record *rec, struct spf_addr *addr)
{
struct spf_dns_cb *cb;
- char *host, *p, mask_buf[3];
- int hostlen;
+ char *host;
CHECK_REC (rec);
return FALSE;
}
begin ++;
-
- bzero (mask_buf, sizeof (mask_buf));
- p = strchr (begin, '/');
- if (p != NULL) {
- /* Extract mask */
- g_strlcpy (mask_buf, p + 1, sizeof (mask_buf));
- addr->mask = mask_buf[0] * 10 + mask_buf[1];
- if (addr->mask > 32) {
- return FALSE;
- }
- hostlen = p - begin;
- host = memory_pool_alloc (task->task_pool, hostlen);
- g_strlcpy (host, begin, hostlen);
- }
- else {
- addr->mask = 32;
- g_strlcpy (host, begin, strlen (begin));
- }
+ host = parse_spf_hostmask (task, begin, addr, rec);
+
+ if (!host) {
+ return FALSE;
+ }
+
rec->dns_requests ++;
cb = memory_pool_alloc (task->task_pool, sizeof (struct spf_dns_cb));
cb->rec = rec;
static gboolean
parse_spf_ptr (struct worker_task *task, const char *begin, struct spf_record *rec, struct spf_addr *addr)
{
- struct spf_dns_cb *cb;
-
CHECK_REC (rec);
-
+
+ msg_info ("parse_spf_ptr: ptr parsing is unimplemented");
+ return FALSE;
}
static gboolean
parse_spf_mx (struct worker_task *task, const char *begin, struct spf_record *rec, struct spf_addr *addr)
{
struct spf_dns_cb *cb;
-
+ char *host;
+
CHECK_REC (rec);
+
+ if (begin == NULL) {
+ return FALSE;
+ }
+ if (*begin == ':') {
+ begin ++;
+ }
+
+ host = parse_spf_hostmask (task, begin, addr, rec);
+
+ if (!host) {
+ return FALSE;
+ }
+
+ rec->dns_requests ++;
+ cb = memory_pool_alloc (task->task_pool, sizeof (struct spf_dns_cb));
+ cb->rec = rec;
+ cb->addr = addr;
+ cb->cur_action = SPF_RESOLVE_MX;
+
+ if (evdns_resolve_mx (host, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+ task->save.saved++;
+ register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
+
+ return TRUE;
+ }
+ return FALSE;
}
static gboolean
/* All is 0/0 */
addr->addr = 0;
addr->mask = 0;
+
+ return TRUE;
}
static gboolean
parse_spf_include (struct worker_task *task, const char *begin, struct spf_record *rec, struct spf_addr *addr)
{
struct spf_dns_cb *cb;
+ char *domain;
CHECK_REC (rec);
+ if (begin == NULL || *begin != ':') {
+ return FALSE;
+ }
+ begin ++;
+ rec->dns_requests ++;
+
+ cb = memory_pool_alloc (task->task_pool, sizeof (struct spf_dns_cb));
+ cb->rec = rec;
+ cb->addr = addr;
+ cb->cur_action = SPF_RESOLVE_REDIRECT;
+ domain = memory_pool_strdup (task->task_pool, begin);
+
+ if (evdns_resolve_txt (domain, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+ task->save.saved++;
+ register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
+
+ return TRUE;
+ }
+
+ return FALSE;
}
static gboolean
parse_spf_exp (struct worker_task *task, const char *begin, struct spf_record *rec, struct spf_addr *addr)
{
- struct spf_dns_cb *cb;
-
CHECK_REC (rec);
+
+ msg_info ("parse_spf_exp: exp record is ignored");
+ return TRUE;
}
static gboolean
parse_spf_redirect (struct worker_task *task, const char *begin, struct spf_record *rec, struct spf_addr *addr)
{
struct spf_dns_cb *cb;
+ char *domain;
CHECK_REC (rec);
+
+ if (begin == NULL || *begin != ':') {
+ return FALSE;
+ }
+ begin ++;
+ rec->dns_requests ++;
+
+ cb = memory_pool_alloc (task->task_pool, sizeof (struct spf_dns_cb));
+ cb->rec = rec;
+ cb->addr = addr;
+ cb->cur_action = SPF_RESOLVE_INCLUDE;
+ domain = memory_pool_strdup (task->task_pool, begin);
+
+ if (evdns_resolve_txt (domain, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+ task->save.saved++;
+ register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
+
+ return TRUE;
+ }
+
+ return FALSE;
}
static gboolean
parse_spf_exists (struct worker_task *task, const char *begin, struct spf_record *rec, struct spf_addr *addr)
{
struct spf_dns_cb *cb;
+ char *host;
CHECK_REC (rec);
+
+ if (begin == NULL || *begin != ':') {
+ return FALSE;
+ }
+ begin ++;
+ rec->dns_requests ++;
+
+ cb = memory_pool_alloc (task->task_pool, sizeof (struct spf_dns_cb));
+ cb->rec = rec;
+ cb->addr = addr;
+ cb->cur_action = SPF_RESOLVE_EXISTS;
+ host = memory_pool_strdup (task->task_pool, begin);
+
+ if (evdns_resolve_ipv4 (host, DNS_QUERY_NO_SEARCH, spf_record_dns_callback, (void *)cb) == 0) {
+ task->save.saved++;
+ register_async_event (task->s, (event_finalizer_t) spf_record_dns_callback, NULL, TRUE);
+
+ return TRUE;
+ }
+
+ return FALSE;
}
/* Read current element and try to parse record */
struct spf_addr *new;
gboolean need_shift, res = FALSE;
char *begin;
-
- if (*rec->cur_elt == NULL) {
+
+ rec->cur_elt = rec->elts[rec->elt_num];
+ if (rec->cur_elt == NULL) {
return TRUE;
}
else {
/* Check spf mech */
new = memory_pool_alloc (task->task_pool, sizeof (struct spf_addr));
- new->mech = check_spf_mech (*rec->cur_elt, &need_shift);
+ new->mech = check_spf_mech (rec->cur_elt, &need_shift);
if (need_shift) {
- begin = (*rec->cur_elt)++;
- }
- else {
- begin = *rec->cur_elt;
+ rec->cur_elt ++;
}
+ begin = rec->cur_elt;
+
+
/* Now check what we have */
switch (*begin) {
case 'a':
res = parse_spf_all (task, begin, rec, new);
}
else {
- msg_info ("parse_spf_record: bad spf command");
+ msg_info ("parse_spf_record: bad spf command: %s", begin);
}
break;
case 'i':
res = parse_spf_include (task, begin, rec, new);
}
else {
- msg_info ("parse_spf_record: bad spf command");
+ msg_info ("parse_spf_record: bad spf command: %s", begin);
}
break;
case 'm':
res = parse_spf_mx (task, begin, rec, new);
}
else {
- msg_info ("parse_spf_record: bad spf command");
+ msg_info ("parse_spf_record: bad spf command: %s", begin);
}
break;
case 'p':
res = parse_spf_ptr (task, begin, rec, new);
}
else {
- msg_info ("parse_spf_record: bad spf command");
+ msg_info ("parse_spf_record: bad spf command: %s", begin);
}
break;
case 'e':
res = parse_spf_exists (task, begin, rec, new);
}
else {
- msg_info ("parse_spf_record: bad spf command");
+ msg_info ("parse_spf_record: bad spf command: %s", begin);
}
break;
case 'r':
res = parse_spf_redirect (task, begin, rec, new);
}
else {
- msg_info ("parse_spf_record: bad spf command");
+ msg_info ("parse_spf_record: bad spf command: %s", begin);
}
break;
default:
- msg_info ("parse_spf_record: bad spf command");
+ msg_info ("parse_spf_record: bad spf command: %s", begin);
break;
}
if (res) {
rec->addrs = g_list_prepend (rec->addrs, new);
- rec->cur_elt ++;
+ rec->elt_num ++;
+ }
+ }
+
+ return res;
+}
+
+static void
+start_spf_parse (struct spf_record *rec, char *begin)
+{
+ if (strncmp (begin, SPF_VER_STR, sizeof (SPF_VER_STR) - 1) == 0) {
+ begin += sizeof (SPF_VER_STR) - 1;
+ while (g_ascii_isspace (*begin) && *begin) {
+ begin ++;
+ }
+ rec->elts = g_strsplit (begin, " ", 0);
+ rec->elt_num = 0;
+ if (rec->elts) {
+ memory_pool_add_destructor (rec->task->task_pool, (pool_destruct_func)g_strfreev, rec->elts);
+ rec->cur_elt = rec->elts[0];
+ while (parse_spf_record (rec->task, rec));
}
}
+ else {
+ msg_info ("start_spf_parse: bad spf record version: %*s", sizeof (SPF_VER_STR) - 1, begin);
+ }
}
static void
if (result == DNS_ERR_NONE && type == DNS_TXT) {
if (addresses != NULL) {
- begin = (char *)addresses;
- if (strncmp (begin, SPF_VER_STR, sizeof (SPF_VER_STR) - 1) == 0) {
- begin += sizeof (SPF_VER_STR) - 1;
- while (g_ascii_isspace (*begin) && *begin) {
- begin ++;
- }
- }
- rec->elts = g_strsplit (begin, " ", 0);
- if (rec->elts) {
- memory_pool_add_destructor (rec->task->task_pool, (pool_destruct_func)g_strfreev, rec->elts);
- rec->cur_elt = rec->elts;
- while (!parse_spf_record (rec->task, rec));
- }
+ begin = *(char **)addresses;
+ start_spf_parse (rec, begin);
}
}
rec->task = task;
rec->callback = callback;
- domain = strchr (task->from, '@');
- if (domain != NULL) {
- rec->cur_domain = memory_pool_strdup (task->task_pool, domain);
+ if (task->from && (domain = strchr (task->from, '@'))) {
+ rec->cur_domain = memory_pool_strdup (task->task_pool, domain + 1);
if ((domain = strchr (rec->cur_domain, '>')) != NULL) {
*domain = '\0';
}
if (domains != NULL) {
rec->cur_domain = memory_pool_strdup (task->task_pool, domains->data);
g_list_free (domains);
+ if ((domain = strchr (rec->cur_domain, '@')) == NULL) {
+ return FALSE;
+ }
+ rec->cur_domain = domain + 1;
+
if ((domain = strchr (rec->cur_domain, '>')) != NULL) {
*domain = '\0';
}