From: Vsevolod Stakhov Date: Mon, 3 Jul 2017 07:47:30 +0000 (+0100) Subject: [Feature] Support SPF macros transformations X-Git-Tag: 1.6.2~35 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6cd82ee7c36488ee629f062e1afa2f9219cef1b1;p=rspamd.git [Feature] Support SPF macros transformations --- diff --git a/src/libserver/spf.c b/src/libserver/spf.c index d2813c272..e8aa9cbab 100644 --- a/src/libserver/spf.c +++ b/src/libserver/spf.c @@ -1355,47 +1355,122 @@ parse_spf_exists (struct spf_record *rec, struct spf_addr *addr) return FALSE; } -static void -reverse_spf_ip (gchar *ip, gint len) +static gsize +rspamd_spf_split_elt (const gchar *val, gsize len, gint *pos, + gsize poslen, gchar delim) { - gchar ipbuf[sizeof ("255.255.255.255") - 1], *p, *c; - gint t = 0, l = len; - - if (len > (gint) sizeof (ipbuf)) { - msg_info ("cannot reverse string of length %d", len); - return; - } - - p = ipbuf + len; - c = ip; - while (--l) { - if (*c == '.') { - memcpy (p, c - t, t); - *--p = '.'; - c++; - t = 0; - continue; + const gchar *p, *end; + guint cur_pos = 0, cur_st = 0, nsub = 0; + + p = val; + end = val + len; + + while (p < end && cur_pos + 2 < poslen) { + if (*p == delim) { + if (p - val > cur_st) { + pos[cur_pos] = cur_st; + pos[cur_pos + 1] = p - val; + cur_st = p - val + 1; + cur_pos += 2; + nsub ++; + } + + p ++; + } + else { + p ++; + } + } + + if (cur_pos + 2 < poslen) { + if (end - val > cur_st) { + pos[cur_pos] = cur_st; + pos[cur_pos + 1] = end - val; + nsub ++; + } + } + else { + pos[cur_pos] = p - val; + pos[cur_pos + 1] = end - val; + nsub ++; + } + + return nsub; +} + +static gsize +rspamd_spf_process_substitution (const gchar *macro_value, + gsize macro_len, guint ndelim, gchar delim, gboolean reversed, + gchar *dest) +{ + gchar *d = dest; + const gchar canon_delim = '.'; + guint vlen, i; + gint pos[49 * 2], tlen; + + if (!reversed && ndelim == 0 && delim == canon_delim) { + /* Trivial case */ + memcpy (dest, macro_value, macro_len); + + return macro_len; + } + + vlen = rspamd_spf_split_elt (macro_value, macro_len, + pos, G_N_ELEMENTS (pos), delim); + + if (vlen > 0) { + if (reversed) { + for (i = vlen - 1; ; i--) { + tlen = pos[i * 2 + 1] - pos[i * 2]; + + if (i != 0) { + memcpy (d, ¯o_value[pos[i * 2]], tlen); + d += tlen; + *d++ = canon_delim; + } + else { + memcpy (d, ¯o_value[pos[i * 2]], tlen); + d += tlen; + break; + } + } + } + else { + for (i = 0; i < vlen; i++) { + tlen = pos[i * 2 + 1] - pos[i * 2]; + + if (i != vlen - 1) { + memcpy (d, ¯o_value[pos[i * 2]], tlen); + d += tlen; + *d++ = canon_delim; + } + else { + memcpy (d, ¯o_value[pos[i * 2]], tlen); + d += tlen; + } + } } + } + else { + /* Trivial case */ + memcpy (dest, macro_value, macro_len); - t++; - c++; - p--; + return macro_len; } - memcpy (p - 1, c - t, t + 1); - memcpy (ip, ipbuf, len); + return (d - dest); } static const gchar * expand_spf_macro (struct spf_record *rec, struct spf_resolved_element *resolved, const gchar *begin) { - const gchar *p; - gchar *c, *new, *tmp; - gsize len = 0, slen = 0; - gint state = 0; - gchar ip_buf[INET6_ADDRSTRLEN]; - gboolean need_expand = FALSE; + const gchar *p, *macro_value; + gchar *c, *new, *tmp, delim; + gsize len = 0, slen = 0, macro_len; + gint state = 0, ndelim = 0; + gchar ip_buf[INET6_ADDRSTRLEN + 1]; + gboolean need_expand = FALSE, reversed; struct rspamd_task *task; g_assert (rec != NULL); @@ -1406,94 +1481,95 @@ expand_spf_macro (struct spf_record *rec, struct spf_resolved_element *resolved, /* Calculate length */ while (*p) { switch (state) { - case 0: - /* Skip any character and wait for % in input */ - if (*p == '%') { - state = 1; - } - else { - len++; - } + case 0: + /* Skip any character and wait for % in input */ + if (*p == '%') { + state = 1; + } + else { + len++; + } - slen++; - p++; + slen++; + p++; + break; + case 1: + /* We got % sign, so we should whether wait for { or for - or for _ or for % */ + if (*p == '%' || *p == '_') { + /* Just a single % sign or space */ + len++; + state = 0; + } + else if (*p == '-') { + /* %20 */ + len += sizeof ("%20") - 1; + state = 0; + } + else if (*p == '{') { + state = 2; + } + else { + /* Something unknown */ + msg_info_spf ( + "<%s>: spf error for domain %s: unknown spf element", + task->message_id, rec->sender_domain); + return begin; + } + p++; + slen++; + break; + case 2: + /* Read macro name */ + switch (g_ascii_tolower (*p)) { + case 'i': + len += INET6_ADDRSTRLEN - 1; break; - case 1: - /* We got % sign, so we should whether wait for { or for - or for _ or for % */ - if (*p == '%' || *p == '_') { - /* Just a single % sign or space */ - len++; - state = 0; - } - else if (*p == '-') { - /* %20 */ - len += sizeof ("%20") - 1; - state = 0; - } - else if (*p == '{') { - state = 2; - } - else { - /* Something unknown */ - msg_info_spf ( - "<%s>: spf error for domain %s: unknown spf element", - task->message_id, rec->sender_domain); - return begin; - } - p++; - slen++; + case 's': + len += strlen (rec->sender); break; - case 2: - /* Read macro name */ - switch (g_ascii_tolower (*p)) { - case 'i': - len += INET6_ADDRSTRLEN - 1; - break; - case 's': - len += strlen (rec->sender); - break; - case 'l': - len += strlen (rec->local_part); - break; - case 'o': - len += strlen (rec->sender_domain); - break; - case 'd': - len += strlen (resolved->cur_domain); - break; - case 'v': - len += sizeof ("in-addr") - 1; - break; - case 'h': - if (task->helo) { - len += strlen (task->helo); - } - break; - default: - msg_info_spf ( - "<%s>: spf error for domain %s: unknown or unsupported spf macro %c in %s", - task->message_id, - rec->sender_domain, - *p, - begin); - return begin; - } - p++; - slen++; - state = 3; + case 'l': + len += strlen (rec->local_part); + break; + case 'o': + len += strlen (rec->sender_domain); + break; + case 'd': + len += strlen (resolved->cur_domain); + break; + case 'v': + len += sizeof ("in-addr") - 1; break; - case 3: - /* Read modifier */ - if (*p == '}') { - state = 0; - need_expand = TRUE; + case 'h': + if (task->helo) { + len += strlen (task->helo); } - p++; - slen++; break; - default: - assert (0); + msg_info_spf ( + "<%s>: spf error for domain %s: unknown or " + "unsupported spf macro %c in %s", + task->message_id, + rec->sender_domain, + *p, + begin); + return begin; + } + p++; + slen++; + state = 3; + break; + case 3: + /* Read modifier */ + if (*p == '}') { + state = 0; + need_expand = TRUE; + } + p++; + slen++; + break; + + default: + assert (0); } } @@ -1511,143 +1587,156 @@ expand_spf_macro (struct spf_record *rec, struct spf_resolved_element *resolved, while (*p) { switch (state) { - case 0: - /* Skip any character and wait for % in input */ - if (*p == '%') { - state = 1; - } - else { - *c = *p; - c++; - } + case 0: + /* Skip any character and wait for % in input */ + if (*p == '%') { + state = 1; + } + else { + *c = *p; + c++; + } - p++; + p++; + break; + case 1: + /* We got % sign, so we should whether wait for { or for - or for _ or for % */ + if (*p == '%') { + /* Just a single % sign or space */ + *c++ = '%'; + state = 0; + } + else if (*p == '_') { + *c++ = ' '; + state = 0; + } + else if (*p == '-') { + /* %20 */ + *c++ = '%'; + *c++ = '2'; + *c++ = '0'; + state = 0; + } + else if (*p == '{') { + state = 2; + } + else { + /* Something unknown */ + msg_info_spf ( + "<%s>: spf error for domain %s: unknown spf element", + task->message_id, rec->sender_domain); + return begin; + } + p++; + break; + case 2: + /* Read macro name */ + switch (g_ascii_tolower (*p)) { + case 'i': + macro_len = rspamd_strlcpy (ip_buf, + rspamd_inet_address_to_string (task->from_addr), + sizeof (ip_buf)); + macro_value = ip_buf; break; - case 1: - /* We got % sign, so we should whether wait for { or for - or for _ or for % */ - if (*p == '%') { - /* Just a single % sign or space */ - *c++ = '%'; - state = 0; - } - else if (*p == '_') { - *c++ = ' '; - state = 0; - } - else if (*p == '-') { - /* %20 */ - *c++ = '%'; - *c++ = '2'; - *c++ = '0'; - state = 0; - } - else if (*p == '{') { - state = 2; + case 's': + macro_len = strlen (rec->sender); + macro_value = rec->sender; + break; + case 'l': + macro_len = strlen (rec->local_part); + macro_value = rec->local_part; + break; + case 'o': + macro_len = strlen (rec->sender_domain); + macro_value = rec->sender_domain; + break; + case 'd': + macro_len = strlen (resolved->cur_domain); + macro_value = resolved->cur_domain; + break; + case 'v': + if (rspamd_inet_address_get_af (task->from_addr) == AF_INET) { + macro_len = sizeof ("in-addr") - 1; + macro_value = "in-addr"; } else { - /* Something unknown */ - msg_info_spf ( - "<%s>: spf error for domain %s: unknown spf element", - task->message_id, rec->sender_domain); - return begin; + macro_len = sizeof ("ip6") - 1; + macro_value = "ip6"; } - p++; break; - case 2: - /* Read macro name */ - switch (g_ascii_tolower (*p)) { - case 'i': - len = rspamd_strlcpy (ip_buf, - rspamd_inet_address_to_string (task->from_addr), - sizeof (ip_buf)); - memcpy (c, ip_buf, len); - c += len; - break; - case 's': - len = strlen (rec->sender); - memcpy (c, rec->sender, len); - c += len; - break; - case 'l': - len = strlen (rec->local_part); - memcpy (c, rec->local_part, len); - c += len; - break; - case 'o': - len = strlen (rec->sender_domain); - memcpy (c, rec->sender_domain, len); - c += len; - break; - case 'd': - len = strlen (resolved->cur_domain); - memcpy (c, resolved->cur_domain, len); - c += len; - break; - case 'v': - if (rspamd_inet_address_get_af (task->from_addr) == AF_INET) { - len = sizeof ("in-addr") - 1; - memcpy (c, "in-addr", len); - } - else { - len = sizeof ("ip6") - 1; - memcpy (c, "ip6", len); - } - c += len; - break; - case 'h': - if (task->helo) { - tmp = strchr (task->helo, '@'); - if (tmp) { - len = strlen (tmp + 1); - memcpy (c, tmp + 1, len); - c += len; - } - else { - len = strlen (task->helo); - memcpy (c, task->helo, len); - c += len; - } - } - break; - default: - msg_info_spf ( - "<%s>: spf error for domain %s: unknown or " - "unsupported spf macro %c in %s", - task->message_id, - rec->sender_domain, - *p, - begin); - return begin; + case 'h': + if (task->helo) { + tmp = strchr (task->helo, '@'); + if (tmp) { + macro_len = strlen (tmp + 1); + macro_value = tmp + 1; + } + else { + macro_len = strlen (task->helo); + macro_value = task->helo; + } } - p++; - state = 3; break; - case 3: - /* Read modifier */ - if (*p == '}') { - state = 0; - } - else if (*p == 'r' && len != 0) { - reverse_spf_ip (c - len, len); - len = 0; - } - else if (g_ascii_isdigit (*p) || *p == '+' || *p == '-' || - *p == '.' || *p == ',' || *p == '/' || *p == '_' || - *p == '=') { - /* TODO: implement domain trimming */ + default: + macro_len = 0; + macro_value = NULL; + msg_info_spf ( + "<%s>: spf error for domain %s: unknown or " + "unsupported spf macro %c in %s", + task->message_id, + rec->sender_domain, + *p, + begin); + return begin; + } + + p++; + state = 3; + ndelim = 0; + delim = '.'; + reversed = FALSE; + break; + + case 3: + /* Read modifier */ + if (*p == '}') { + state = 0; + len = rspamd_spf_process_substitution (macro_value, + macro_len, ndelim, delim, reversed, c); + c += len; + } + else if (*p == 'r' && len != 0) { + reversed = TRUE; + } + else if (g_ascii_isdigit (*p)) { + ndelim = strtoul (p, &tmp, 10); + + if (tmp == NULL || tmp == p) { + p ++; } else { - msg_info_spf ( - "<%s>: spf error for domain %s: unknown or " - "unsupported spf macro %c in %s", - task->message_id, - rec->sender_domain, - *p, - begin); - return begin; + p = tmp; + + continue; } - p++; - break; + } + else if (*p == '+' || *p == '-' || + *p == '.' || *p == ',' || *p == '/' || *p == '_' || + *p == '=') { + delim = *p; + } + else { + msg_info_spf ( + "<%s>: spf error for domain %s: unknown or " + "unsupported spf macro %c (%s) in %s", + task->message_id, + rec->sender_domain, + *p, + begin); + return begin; + } + p++; + break; } } /* Null terminate */