aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/lua/spamassassin.lua176
1 files changed, 158 insertions, 18 deletions
diff --git a/src/plugins/lua/spamassassin.lua b/src/plugins/lua/spamassassin.lua
index 35e49dce1..d93104486 100644
--- a/src/plugins/lua/spamassassin.lua
+++ b/src/plugins/lua/spamassassin.lua
@@ -31,11 +31,20 @@ local rspamd_logger = require "rspamd_logger"
local rspamd_regexp = require "rspamd_regexp"
local rspamd_expression = require "rspamd_expression"
local rspamd_mempool = require "rspamd_mempool"
+local rspamd_trie = require "rspamd_trie"
local _ = require "fun"
+
--local dumper = require 'pl.pretty'.dump
+
+-- Known plugins
+local known_plugins = {'Mail::SpamAssassin::Plugin::FreeMail'}
+
+-- Internal variables
local rules = {}
local atoms = {}
local metas = {}
+local freemail_domains = {}
+local freemail_trie
local section = rspamd_config:get_key("spamassassin")
-- Minimum score to treat symbols as meta
@@ -123,6 +132,125 @@ local function handle_header_def(hline, cur_rule)
end
end
+
+local function freemail_search(input)
+ local res = false
+ local function trie_callback(number, pos)
+ rspamd_logger.debugx('Matched pattern %1 at pos %2', freemail_domains[number], pos)
+ res = true
+ end
+
+ if input then
+ freemail_trie:match(input, trie_callback, true)
+ end
+
+ return res
+end
+
+local function gen_eval_rule(arg)
+ local eval_funcs = {
+ {'check_freemail_from', function(task, remain)
+ local from = task:get_from()
+ if from then
+ return freemail_search(from[1]['addr'])
+ end
+ return false
+ end},
+ {'check_freemail_replyto',
+ function(task, remain)
+ return freemail_search(task:get_header('Reply-To'))
+ end
+ },
+ {'check_freemail_header',
+ function(task, remain)
+ -- Remain here contains one or two args: header and regexp to match
+ local arg = string.match(remain, "^%(%s*['\"]([^%s]+)['\"]%s*%)$")
+ local re = nil
+ if not arg then
+ arg, re = string.match(remain, "^%(%s*['\"]([^%s]+)['\"]%s*,%s*['\"]([^%s]+)['\"]%s*%)$")
+ end
+
+ if arg then
+ local h = task:get_header(arg)
+ if h then
+ local hdr_freemail = freemail_search(h)
+ if hdr_freemail and re then
+ r = rspamd_regexp.create_cached(re)
+ if r then
+ r:match(h)
+ else
+ rspamd_logger.infox('cannot create regexp %1', re)
+ return false
+ end
+ end
+
+ return hdr_freemail
+ end
+ end
+
+ return false
+ end
+ },
+ }
+
+ for k,f in ipairs(eval_funcs) do
+ local pat = string.format('^%s', f[1])
+ local first,last = string.find(arg, pat)
+
+ if first then
+ local func_arg = string.sub(arg, last + 1)
+ return function(task)
+ return f[2](task, func_arg)
+ end
+ end
+ end
+end
+
+-- Returns parser function or nil
+local function maybe_parse_sa_function(line)
+ local arg
+ local elts = split(line, '[^:]+')
+ arg = elts[2]
+ local func_cache = {}
+
+ rspamd_logger.debugx('trying to parse SA function %1 with args %2', elts[1], elts[2])
+ local substitutions = {
+ {'^exists:',
+ function(task) -- filter
+ if task:get_header(arg) then
+ return true
+ end
+ return false
+ end,
+ },
+ {'^eval:',
+ function(task)
+ local func = func_cache[arg]
+ if not func then
+ func = gen_eval_rule(arg)
+ func_cache[arg] = func
+ end
+
+ if not func then
+ rspamd_logger.errx('cannot find appropriate eval rule for function %1', arg)
+ else
+ return func(task)
+ end
+
+ return false
+ end
+ },
+ }
+
+ for k,s in ipairs(substitutions) do
+ if string.find(line, s[1]) then
+ return s[2]
+ end
+ end
+
+ return nil
+end
+
local function process_sa_conf(f)
local cur_rule = {}
local valid_rule = false
@@ -153,7 +281,14 @@ local function process_sa_conf(f)
return
else
if string.match(l, '^ifplugin') then
- skip_to_endif = true
+ local ls = split(l)
+
+ if not _.any(function(pl)
+ if pl == ls[2] then return true end
+ return false
+ end, known_plugins) then
+ skip_to_endif = true
+ end
end
end
@@ -171,7 +306,7 @@ local function process_sa_conf(f)
if valid_rule then
insert_cur_rule()
end
- if slash then
+ if slash and words[4] and (words[4] == '=~' or words[4] == '!~') then
cur_rule['type'] = 'header'
cur_rule['symbol'] = words[2]
@@ -205,20 +340,16 @@ local function process_sa_conf(f)
end
else
-- Maybe we know the function and can convert it
- local s,e = string.find(words[3], 'exists:')
- if e then
- local h = _.foldl(function(acc, s) return acc .. s end,
- '', _.drop_n(e, words[3]))
- cur_rule['type'] = 'function'
- cur_rule['symbol'] = words[2]
- cur_rule['header'] = h
- cur_rule['function'] = function(task)
- if task:get_header(h) then
- return true
- end
- return false
- end
- valid_rule = true
+ local args = words_to_re(words, 2)
+ local func = maybe_parse_sa_function(args)
+
+ if func then
+ cur_rule['type'] = 'function'
+ cur_rule['symbol'] = words[2]
+ cur_rule['function'] = func
+ valid_rule = true
+ else
+ rspamd_logger.infox('unknown function %1', args)
end
end
elseif words[1] == "body" and slash then
@@ -261,9 +392,13 @@ local function process_sa_conf(f)
cur_rule['meta'] = words_to_re(words, 2)
if cur_rule['meta'] and cur_rule['symbol'] then valid_rule = true end
elseif words[1] == "describe" and valid_rule then
- cur_rule['description'] = words_to_re(words, 1)
+ cur_rule['description'] = words_to_re(words, 2)
elseif words[1] == "score" and valid_rule then
cur_rule['score'] = tonumber(words_to_re(words, 2))
+ elseif words[1] == 'freemail_domains' then
+ _.each(function(dom)
+ table.insert(freemail_domains, '@' .. dom)
+ end, _.drop_n(1, words))
end
end)()
end
@@ -304,6 +439,11 @@ local function add_sole_meta(sym, rule)
rules[sym] = r
end
+if freemail_domains then
+ freemail_trie = rspamd_trie.create(freemail_domains)
+ rspamd_logger.infox('loaded %1 freemail domains definitions', #freemail_domains)
+end
+
-- Header rules
_.each(function(k, r)
local f = function(task)
@@ -489,7 +629,7 @@ local function process_atom(atom, task)
end
return res
else
- --rspamd_logger.err('Cannot find atom ' .. atom)
+ rspamd_logger.err('Cannot find atom ' .. atom)
end
return 0
end