From 5791ea36ec234aabcd4fb8d4c9bb0b7ddb08209d Mon Sep 17 00:00:00 2001 From: Andrew Lewis Date: Fri, 20 Jan 2017 11:30:36 +0200 Subject: [PATCH] [Minor] Rework rmilter headers internal functions --- src/plugins/lua/rmilter_headers.lua | 427 ++++++++++++++-------------- 1 file changed, 209 insertions(+), 218 deletions(-) diff --git a/src/plugins/lua/rmilter_headers.lua b/src/plugins/lua/rmilter_headers.lua index d8083ea70..86acaf950 100644 --- a/src/plugins/lua/rmilter_headers.lua +++ b/src/plugins/lua/rmilter_headers.lua @@ -82,246 +82,236 @@ local settings = { } local active_routines = {} -local routines = {} +local custom_routines = {} -routines['x-spamd-bar'] = function(task, common_meta) - local common, add, remove = {}, {}, {} - if not common_meta['metric_score'] then - common['metric_score'] = task:get_metric_score('default') - common_meta['metric_score'] = common['metric_score'] - end - local score = common_meta['metric_score'][1] - local spambar - if score <= -1 then - spambar = string.rep(settings.routines['x-spamd-bar'].negative, score*-1) - elseif score >= 1 then - spambar = string.rep(settings.routines['x-spamd-bar'].positive, score) - else - spambar = settings.routines['x-spamd-bar'].neutral - end - if settings.routines['x-spamd-bar'].remove then - remove[settings.routines['x-spamd-bar'].header] = settings.routines['x-spamd-bar'].remove - end - if spambar ~= '' then - add[settings.routines['x-spamd-bar'].header] = spambar - end - return nil, add, remove, common -end +local function rmilter_headers(task) -routines['x-spam-level'] = function(task, common_meta) - local common, add, remove = {}, {}, {} - if not common_meta['metric_score'] then - common['metric_score'] = task:get_metric_score('default') - common_meta['metric_score'] = common['metric_score'] - end - local score = common_meta['metric_score'][1] - if score < 1 then - return nil, {}, {}, common - end - if settings.routines['x-spam-level'].remove then - remove[settings.routines['x-spam-level'].header] = settings.routines['x-spam-level'].remove - end - add[settings.routines['x-spam-level'].header] = string.rep(settings.routines['x-spam-level'].char, score) - return nil, add, remove, common -end + local routines, common, add, remove = {}, {}, {}, {} -routines['spam-header'] = function(task, common_meta) - local common, add, remove = {}, {}, {} - if not common_meta['metric_action'] then - common['metric_action'] = task:get_metric_action('default') - common_meta['metric_action'] = common['metric_action'] - end - if settings.routines['spam-header'].remove then - remove[settings.routines['spam-header'].header] = settings.routines['spam-header'].remove - end - local action = common_meta['metric_action'] - if action ~= 'no action' and action ~= 'greylist' then - add[settings.routines['spam-header'].header] = settings.routines['spam-header'].value + routines['x-spamd-bar'] = function() + if not common['metric_score'] then + common['metric_score'] = task:get_metric_score('default') + end + local score = common['metric_score'][1] + local spambar + if score <= -1 then + spambar = string.rep(settings.routines['x-spamd-bar'].negative, score*-1) + elseif score >= 1 then + spambar = string.rep(settings.routines['x-spamd-bar'].positive, score) + else + spambar = settings.routines['x-spamd-bar'].neutral + end + if settings.routines['x-spamd-bar'].remove then + remove[settings.routines['x-spamd-bar'].header] = settings.routines['x-spamd-bar'].remove + end + if spambar ~= '' then + add[settings.routines['x-spamd-bar'].header] = spambar + end end - return nil, add, remove, common -end -routines['x-virus'] = function(task, common_meta) - local add, remove = {}, {} - local common = {symbols = {}} - if not common_meta.symbols then - common_meta.symbols = {} + routines['x-spam-level'] = function() + if not common['metric_score'] then + common['metric_score'] = task:get_metric_score('default') + end + local score = common['metric_score'][1] + if score < 1 then + return nil, {}, {} + end + if settings.routines['x-spam-level'].remove then + remove[settings.routines['x-spam-level'].header] = settings.routines['x-spam-level'].remove + end + add[settings.routines['x-spam-level'].header] = string.rep(settings.routines['x-spam-level'].char, score) end - if settings.routines['x-virus'].remove then - remove[settings.routines['x-virus'].header] = settings.routines['x-virus'].remove + + routines['spam-header'] = function() + if not common['metric_action'] then + common['metric_action'] = task:get_metric_action('default') + end + if settings.routines['spam-header'].remove then + remove[settings.routines['spam-header'].header] = settings.routines['spam-header'].remove + end + local action = common['metric_action'] + if action ~= 'no action' and action ~= 'greylist' then + add[settings.routines['spam-header'].header] = settings.routines['spam-header'].value + end end - local virii = {} - for _, sym in ipairs(settings.routines['x-virus'].symbols) do - if not (common_meta.symbols[sym] == false) then - local s = task:get_symbol(sym) - if not s then - common_meta.symbols[sym] = false - common[sym] = false - else - common_meta.symbols[sym] = s - common[sym] = s - if (((s or E)[1] or E).options or E)[1] then - table.insert(virii, s[1].options[1]) - else - table.insert(virii, 'unknown') - end + + routines['x-virus'] = function() + if not common.symbols then + common.symbols = {} + end + if settings.routines['x-virus'].remove then + remove[settings.routines['x-virus'].header] = settings.routines['x-virus'].remove + end + local virii = {} + for _, sym in ipairs(settings.routines['x-virus'].symbols) do + if not (common.symbols[sym] == false) then + local s = task:get_symbol(sym) + if not s then + common.symbols[sym] = false + else + common.symbols[sym] = s + if (((s or E)[1] or E).options or E)[1] then + table.insert(virii, s[1].options[1]) + else + table.insert(virii, 'unknown') + end + end end end + if #virii > 0 then + add[settings.routines['x-virus'].header] = table.concat(virii, ',') + end end - if #virii > 0 then - add[settings.routines['x-virus'].header] = table.concat(virii, ',') - end - return nil, add, remove, common -end -routines['x-spam-status'] = function(task, common_meta) - local common, add, remove = {}, {}, {} - if not common_meta['metric_score'] then - common['metric_score'] = task:get_metric_score('default') - common_meta['metric_score'] = common['metric_score'] - end - if not common_meta['metric_action'] then - common['metric_action'] = task:get_metric_action('default') - common_meta['metric_action'] = common['metric_action'] - end - local score = common_meta['metric_score'][1] - local action = common_meta['metric_action'] - local is_spam - local spamstatus - if action ~= 'no action' and action ~= 'greylist' then - is_spam = 'Yes' - else - is_spam = 'No' - end - spamstatus = is_spam .. ', score=' .. string.format('%.2f', score) - if settings.routines['x-spam-status'].remove then - remove[settings.routines['x-spam-status'].header] = settings.routines['x-spam-status'].remove + routines['x-spam-status'] = function() + if not common['metric_score'] then + common['metric_score'] = task:get_metric_score('default') + end + if not common['metric_action'] then + common['metric_action'] = task:get_metric_action('default') + end + local score = common['metric_score'][1] + local action = common['metric_action'] + local is_spam + local spamstatus + if action ~= 'no action' and action ~= 'greylist' then + is_spam = 'Yes' + else + is_spam = 'No' + end + spamstatus = is_spam .. ', score=' .. string.format('%.2f', score) + if settings.routines['x-spam-status'].remove then + remove[settings.routines['x-spam-status'].header] = settings.routines['x-spam-status'].remove + end + add[settings.routines['x-spam-status'].header] = spamstatus end - add[settings.routines['x-spam-status'].header] = spamstatus - return nil, add, remove, common -end -routines['authentication-results'] = function(task, common_meta) - local add, remove, auth_results, hdr_parts = {}, {}, {}, {} - local common = {symbols = {}} - local auth_types = { - dkim = settings.routines['authentication-results'].dkim_symbols, - dmarc = settings.routines['authentication-results'].dmarc_symbols, - spf = settings.routines['authentication-results'].spf_symbols, - } - if not common_meta.symbols then common_meta.symbols = {} end - for auth_type, symbols in pairs(auth_types) do - for key, sym in pairs(symbols) do - if not (common_meta.symbols[sym] == false) then - local s = task:get_symbol(sym) - if not s then - common_meta.symbols[sym] = false - common.symbols[sym] = false - else - common_meta.symbols[sym] = s - common.symbols[sym] = s - if not auth_results[auth_type] then - auth_results[auth_type] = {key} - else - table.insert(auth_results[auth_type], key) - end - if auth_type ~= 'dkim' then - break - end - end + routines['authentication-results'] = function() + local auth_results, hdr_parts = {}, {} + if not common.symbols then + common.symbols = {} + end + local auth_types = { + dkim = settings.routines['authentication-results'].dkim_symbols, + dmarc = settings.routines['authentication-results'].dmarc_symbols, + spf = settings.routines['authentication-results'].spf_symbols, + } + for auth_type, symbols in pairs(auth_types) do + for key, sym in pairs(symbols) do + if not (common.symbols[sym] == false) then + local s = task:get_symbol(sym) + if not s then + common.symbols[sym] = false + else + common.symbols[sym] = s + if not auth_results[auth_type] then + auth_results[auth_type] = {key} + else + table.insert(auth_results[auth_type], key) + end + if auth_type ~= 'dkim' then + break + end + end + end end end + if settings.routines['authentication-results'].remove then + remove[settings.routines['authentication-results'].header] = settings.routines['authentication-results'].remove + end + for auth_type, keys in pairs(auth_results) do + for _, key in ipairs(keys) do + local hdr = '' + if auth_type == 'dmarc' and key ~= 'none' then + hdr = hdr .. 'dmarc=' + if key == 'reject' or key == 'quarantine' or key == 'softfail' then + hdr = hdr .. 'fail' + else + hdr = hdr .. key + end + if key == 'pass' then + hdr = hdr .. ' policy=' .. common.symbols[auth_types['dmarc'][key]][1]['options'][2] + hdr = hdr .. ' header.from=' .. common.symbols[auth_types['dmarc'][key]][1]['options'][1] + elseif key ~= 'none' then + local t = rspamd_str_split(common.symbols[auth_types['dmarc'][key]][1]['options'][1], ' : ') + local dom = t[1] + local rsn = t[2] + hdr = hdr .. ' reason="' .. rsn .. '"' + hdr = hdr .. ' header.from=' .. dom + if key == 'softfail' then + hdr = hdr .. ' policy=none' + else + hdr = hdr .. ' policy=' .. key + end + end + table.insert(hdr_parts, hdr) + elseif auth_type == 'dkim' and key ~= 'none' then + if common.symbols[auth_types['dkim'][key]][1] then + for _, v in ipairs(common.symbols[auth_types['dkim'][key]][1]['options']) do + hdr = hdr .. auth_type .. '=' .. key .. ' header.d=' .. v + table.insert(hdr_parts, hdr) + end + end + elseif auth_type == 'spf' and key ~= 'none' then + hdr = hdr .. auth_type .. '=' .. key + local smtp_from = task:get_from('smtp') + if smtp_from['addr'] ~= '' and smtp_from['addr'] ~= nil then + hdr = hdr .. ' smtp.mailfrom=' .. smtp_from['addr'] + else + local helo = task:get_helo() + if helo then + hdr = hdr .. ' smtp.helo=' .. task:get_helo() + end + end + table.insert(hdr_parts, hdr) + end + end + end + if #hdr_parts > 0 then + add[settings.routines['authentication-results'].header] = table.concat(hdr_parts, '; ') + end end - if settings.routines['authentication-results'].remove then - remove[settings.routines['authentication-results'].header] = settings.routines['authentication-results'].remove - end - for auth_type, keys in pairs(auth_results) do - for _, key in ipairs(keys) do - local hdr = '' - if auth_type == 'dmarc' and key ~= 'none' then - hdr = hdr .. 'dmarc=' - if key == 'reject' or key == 'quarantine' or key == 'softfail' then - hdr = hdr .. 'fail' - else - hdr = hdr .. key - end - if key == 'pass' then - hdr = hdr .. ' policy=' .. common_meta.symbols[auth_types['dmarc'][key]][1]['options'][2] - hdr = hdr .. ' header.from=' .. common_meta.symbols[auth_types['dmarc'][key]][1]['options'][1] - elseif key ~= 'none' then - local t = rspamd_str_split(common_meta.symbols[auth_types['dmarc'][key]][1]['options'][1], ' : ') - local dom = t[1] - local rsn = t[2] - hdr = hdr .. ' reason="' .. rsn .. '"' - hdr = hdr .. ' header.from=' .. dom - if key == 'softfail' then - hdr = hdr .. ' policy=none' - else - hdr = hdr .. ' policy=' .. key - end + + for _, n in ipairs(active_routines) do + local ok, err + if custom_routines[n] then + local to_add, to_remove, common_in + ok, err, to_add, to_remove, common_in = pcall(custom_routines[n], task, common) + if ok then + for k, v in pairs(to_add) do + add[k] = v end - table.insert(hdr_parts, hdr) - elseif auth_type == 'dkim' and key ~= 'none' then - if common_meta.symbols[auth_types['dkim'][key]][1] then - for _, v in ipairs(common_meta.symbols[auth_types['dkim'][key]][1]['options']) do - hdr = hdr .. auth_type .. '=' .. key .. ' header.d=' .. v - table.insert(hdr_parts, hdr) - end + for k, v in pairs(to_remove) do + add[k] = v end - elseif auth_type == 'spf' and key ~= 'none' then - hdr = hdr .. auth_type .. '=' .. key - local smtp_from = task:get_from('smtp') - if smtp_from['addr'] ~= '' and smtp_from['addr'] ~= nil then - hdr = hdr .. ' smtp.mailfrom=' .. smtp_from['addr'] - else - local helo = task:get_helo() - if helo then - hdr = hdr .. ' smtp.helo=' .. task:get_helo() + for k, v in pairs(common_in) do + if type(v) == 'table' then + if not common[k] then + common[k] = {} + end + for kk, vv in pairs(v) do + common[k][kk] = vv + end + else + common[k] = v end end - table.insert(hdr_parts, hdr) end + else + ok, err = pcall(routines[n]) end - end - if #hdr_parts > 0 then - add[settings.routines['authentication-results'].header] = table.concat(hdr_parts, '; ') - end - return nil, add, remove, common -end - -local function rmilter_headers(task) - local common_meta, to_add, to_remove = {}, {}, {} - for n, f in pairs(active_routines) do - local ok, err, add, remove, common = pcall(routines[f], task, common_meta) if not ok then logger.errx(task, 'call to %s failed: %s', n, err) - else - for k, v in pairs(add) do - to_add[k] = v - end - for k, v in pairs(remove) do - to_remove[k] = v - end - for k, v in pairs(common) do - if type(v) == 'table' then - if not common_meta[k] then - common_meta[k] = {} - end - for sk, sv in pairs(v) do - common_meta[k][sk] = sv - end - else - common_meta[k] = v - end - end end end - if not next(to_add) then to_add = nil end - if not next(to_remove) then to_remove = nil end - if to_add or to_remove then + + if not next(add) then add = nil end + if not next(remove) then remove = nil end + if add or remove then task:set_rmilter_reply({ - add_headers = to_add, - remove_headers = to_remove + add_headers = add, + remove_headers = remove }) end end @@ -338,29 +328,30 @@ end if type(opts['custom']) == 'table' then for k, v in pairs(opts['custom']) do local f, err = load(v) - if err then + if not f then logger.errx(rspamd_config, 'could not load "%s": %s', k, err) else - routines[k] = f + custom_routines[k] = f() end end end for _, s in ipairs(opts['use']) do - if not routines[s] then - logger.errx(rspamd_config, 'routine "%s" does not exist', s) - else + if (opts.routines and opts.routines[s]) or custom_routines[s] then table.insert(active_routines, s) if (opts.routines and opts.routines[s]) then for k, v in pairs(opts.routines[s]) do settings.routines[s][k] = v end end + else + logger.errx(rspamd_config, 'routine "%s" does not exist', s) end end if (#active_routines < 1) then logger.errx(rspamd_config, 'no active routines') return end +logger.infox(rspamd_config, 'active routines [%s]', table.concat(active_routines, ',')) rspamd_config:register_symbol({ name = 'RMILTER_HEADERS', type = 'postfilter', -- 2.39.5