local rspamd_logger = require "rspamd_logger" | local rspamd_logger = require "rspamd_logger" | ||||
local rspamd_http = require "rspamd_http" | local rspamd_http = require "rspamd_http" | ||||
local lua_util = require "lua_util" | |||||
local exports = {} | local exports = {} | ||||
local N = 'clickhouse' | local N = 'clickhouse' | ||||
-- Parses JSONEachRow reply from CH | -- Parses JSONEachRow reply from CH | ||||
local function parse_clickhouse_response(params, data) | local function parse_clickhouse_response(params, data) | ||||
local lua_util = require "lua_util" | |||||
local ucl = require "ucl" | local ucl = require "ucl" | ||||
if data == nil then | if data == nil then | ||||
if ok_cb then | if ok_cb then | ||||
ok_cb(params, rows) | ok_cb(params, rows) | ||||
else | else | ||||
rspamd_logger.debugm(N, params.log_obj, | |||||
lua_util.debugm(N, params.log_obj, | |||||
"http_select_cb ok: %s, %s, %s, %s", err_message, code, | "http_select_cb ok: %s, %s, %s, %s", err_message, code, | ||||
data:gsub('[\n%s]+', ' '), _) | data:gsub('[\n%s]+', ' '), _) | ||||
end | end | ||||
if ok_cb then | if ok_cb then | ||||
ok_cb(params, data) | ok_cb(params, data) | ||||
else | else | ||||
rspamd_logger.debugm(N, params.log_obj, | |||||
lua_util.debugm(N, params.log_obj, | |||||
"http_insert_cb ok: %s, %s, %s, %s", err_message, code, | "http_insert_cb ok: %s, %s, %s, %s", err_message, code, | ||||
data:gsub('[\n%s]+', ' '), _) | data:gsub('[\n%s]+', ' '), _) | ||||
end | end | ||||
http_params.body = query | http_params.body = query | ||||
http_params.log_obj = params.task or params.config | http_params.log_obj = params.task or params.config | ||||
rspamd_logger.debugm(N, http_params.log_obj, "clickhouse select request: %s", http_params.body) | |||||
lua_util.debugm(N, http_params.log_obj, "clickhouse select request: %s", http_params.body) | |||||
if not http_params.url then | if not http_params.url then | ||||
local connect_prefix = "http://" | local connect_prefix = "http://" |
local exports = {} | local exports = {} | ||||
local E = {} | local E = {} | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local lua_util = require "lua_util" | |||||
local rspamd_util = require "rspamd_util" | local rspamd_util = require "rspamd_util" | ||||
local function prepare_dkim_signing(N, task, settings) | local function prepare_dkim_signing(N, task, settings) | ||||
end | end | ||||
if settings.auth_only and auser then | if settings.auth_only and auser then | ||||
rspamd_logger.debugm(N, task, 'user is authenticated') | |||||
lua_util.debugm(N, task, 'user is authenticated') | |||||
elseif (settings.sign_networks and settings.sign_networks:get_key(ip)) then | elseif (settings.sign_networks and settings.sign_networks:get_key(ip)) then | ||||
is_sign_networks = true | is_sign_networks = true | ||||
rspamd_logger.debugm(N, task, 'mail is from address in sign_networks') | |||||
lua_util.debugm(N, task, 'mail is from address in sign_networks') | |||||
elseif settings.sign_local and is_local then | elseif settings.sign_local and is_local then | ||||
rspamd_logger.debugm(N, task, 'mail is from local address') | |||||
lua_util.debugm(N, task, 'mail is from local address') | |||||
elseif settings.sign_inbound and not is_local and not auser then | elseif settings.sign_inbound and not is_local and not auser then | ||||
rspamd_logger.debugm(N, task, 'mail was sent to us') | |||||
lua_util.debugm(N, task, 'mail was sent to us') | |||||
else | else | ||||
rspamd_logger.debugm(N, task, 'ignoring unauthenticated mail') | |||||
lua_util.debugm(N, task, 'ignoring unauthenticated mail') | |||||
return false,{} | return false,{} | ||||
end | end | ||||
local efrom = task:get_from('smtp') | local efrom = task:get_from('smtp') | ||||
if not settings.allow_envfrom_empty and | if not settings.allow_envfrom_empty and | ||||
#(((efrom or E)[1] or E).addr or '') == 0 then | #(((efrom or E)[1] or E).addr or '') == 0 then | ||||
rspamd_logger.debugm(N, task, 'empty envelope from not allowed') | |||||
lua_util.debugm(N, task, 'empty envelope from not allowed') | |||||
return false,{} | return false,{} | ||||
end | end | ||||
local hfrom = task:get_from('mime') | local hfrom = task:get_from('mime') | ||||
if not settings.allow_hdrfrom_multiple and (hfrom or E)[2] then | if not settings.allow_hdrfrom_multiple and (hfrom or E)[2] then | ||||
rspamd_logger.debugm(N, task, 'multiple header from not allowed') | |||||
lua_util.debugm(N, task, 'multiple header from not allowed') | |||||
return false,{} | return false,{} | ||||
end | end | ||||
if settings.use_domain_sign_networks and is_sign_networks then | if settings.use_domain_sign_networks and is_sign_networks then | ||||
dkim_domain = get_dkim_domain('use_domain_sign_networks') | dkim_domain = get_dkim_domain('use_domain_sign_networks') | ||||
rspamd_logger.debugm(N, task, 'sign_networks: use domain(%s) for signature: %s', | |||||
lua_util.debugm(N, task, 'sign_networks: use domain(%s) for signature: %s', | |||||
settings.use_domain_sign_networks, dkim_domain) | settings.use_domain_sign_networks, dkim_domain) | ||||
elseif settings.use_domain_sign_local and is_local then | elseif settings.use_domain_sign_local and is_local then | ||||
dkim_domain = get_dkim_domain('use_domain_sign_local') | dkim_domain = get_dkim_domain('use_domain_sign_local') | ||||
rspamd_logger.debugm(N, task, 'local: use domain(%s) for signature: %s', | |||||
lua_util.debugm(N, task, 'local: use domain(%s) for signature: %s', | |||||
settings.use_domain_sign_local, dkim_domain) | settings.use_domain_sign_local, dkim_domain) | ||||
elseif settings.use_domain_sign_inbound and not is_local and not auser then | elseif settings.use_domain_sign_inbound and not is_local and not auser then | ||||
dkim_domain = get_dkim_domain('use_domain_sign_inbound') | dkim_domain = get_dkim_domain('use_domain_sign_inbound') | ||||
rspamd_logger.debugm(N, task, 'inbound: use domain(%s) for signature: %s', | |||||
lua_util.debugm(N, task, 'inbound: use domain(%s) for signature: %s', | |||||
settings.use_domain_sign_inbound, dkim_domain) | settings.use_domain_sign_inbound, dkim_domain) | ||||
else | else | ||||
dkim_domain = get_dkim_domain('use_domain') | dkim_domain = get_dkim_domain('use_domain') | ||||
rspamd_logger.debugm(N, task, 'use domain(%s) for signature: %s', | |||||
lua_util.debugm(N, task, 'use domain(%s) for signature: %s', | |||||
settings.use_domain, dkim_domain) | settings.use_domain, dkim_domain) | ||||
end | end | ||||
if not dkim_domain then | if not dkim_domain then | ||||
rspamd_logger.debugm(N, task, 'could not extract dkim domain') | |||||
lua_util.debugm(N, task, 'could not extract dkim domain') | |||||
return false,{} | return false,{} | ||||
end | end | ||||
end | end | ||||
end | end | ||||
rspamd_logger.debugm(N, task, 'final DKIM domain: %s', dkim_domain) | |||||
lua_util.debugm(N, task, 'final DKIM domain: %s', dkim_domain) | |||||
if edom and hdom and not settings.allow_hdrfrom_mismatch and hdom ~= edom then | if edom and hdom and not settings.allow_hdrfrom_mismatch and hdom ~= edom then | ||||
if settings.allow_hdrfrom_mismatch_local and is_local then | if settings.allow_hdrfrom_mismatch_local and is_local then | ||||
rspamd_logger.debugm(N, task, 'domain mismatch allowed for local IP: %1 != %2', hdom, edom) | |||||
lua_util.debugm(N, task, 'domain mismatch allowed for local IP: %1 != %2', hdom, edom) | |||||
elseif settings.allow_hdrfrom_mismatch_sign_networks and is_sign_networks then | elseif settings.allow_hdrfrom_mismatch_sign_networks and is_sign_networks then | ||||
rspamd_logger.debugm(N, task, 'domain mismatch allowed for sign_networks: %1 != %2', hdom, edom) | |||||
lua_util.debugm(N, task, 'domain mismatch allowed for sign_networks: %1 != %2', hdom, edom) | |||||
else | else | ||||
rspamd_logger.debugm(N, task, 'domain mismatch not allowed: %1 != %2', hdom, edom) | |||||
lua_util.debugm(N, task, 'domain mismatch not allowed: %1 != %2', hdom, edom) | |||||
return false,{} | return false,{} | ||||
end | end | ||||
end | end | ||||
if auser and not settings.allow_username_mismatch then | if auser and not settings.allow_username_mismatch then | ||||
if not udom then | if not udom then | ||||
rspamd_logger.debugm(N, task, 'couldnt find domain in username') | |||||
lua_util.debugm(N, task, 'couldnt find domain in username') | |||||
return false,{} | return false,{} | ||||
end | end | ||||
if settings.use_esld then | if settings.use_esld then | ||||
udom = rspamd_util.get_tld(udom) | udom = rspamd_util.get_tld(udom) | ||||
end | end | ||||
if udom ~= dkim_domain then | if udom ~= dkim_domain then | ||||
rspamd_logger.debugm(N, task, 'user domain mismatch') | |||||
lua_util.debugm(N, task, 'user domain mismatch') | |||||
return false,{} | return false,{} | ||||
end | end | ||||
end | end | ||||
if (not p.key or not p.selector) and (not (settings.try_fallback or | if (not p.key or not p.selector) and (not (settings.try_fallback or | ||||
settings.use_redis or settings.selector_map | settings.use_redis or settings.selector_map | ||||
or settings.path_map)) then | or settings.path_map)) then | ||||
rspamd_logger.debugm(N, task, 'dkim unconfigured and fallback disabled') | |||||
lua_util.debugm(N, task, 'dkim unconfigured and fallback disabled') | |||||
return false,{} | return false,{} | ||||
end | end | ||||
end | end | ||||
if data then | if data then | ||||
p.selector = data | p.selector = data | ||||
elseif not settings.try_fallback then | elseif not settings.try_fallback then | ||||
rspamd_logger.debugm(N, task, 'no selector for %s', dkim_domain) | |||||
lua_util.debugm(N, task, 'no selector for %s', dkim_domain) | |||||
return false,{} | return false,{} | ||||
end | end | ||||
end | end | ||||
if data then | if data then | ||||
p.key = data | p.key = data | ||||
elseif not settings.try_fallback then | elseif not settings.try_fallback then | ||||
rspamd_logger.debugm(N, task, 'no key for %s', dkim_domain) | |||||
lua_util.debugm(N, task, 'no key for %s', dkim_domain) | |||||
return false,{} | return false,{} | ||||
end | end | ||||
end | end |
} | } | ||||
local function rspamd_gen_metatokens(task) | local function rspamd_gen_metatokens(task) | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local lua_util = require "lua_util" | |||||
local ipairs = ipairs | local ipairs = ipairs | ||||
local metatokens = {} | local metatokens = {} | ||||
local cached = task:cache_get('metatokens') | local cached = task:cache_get('metatokens') | ||||
for _,mt in ipairs(metafunctions) do | for _,mt in ipairs(metafunctions) do | ||||
local ct = mt.cb(task) | local ct = mt.cb(task) | ||||
for i,tok in ipairs(ct) do | for i,tok in ipairs(ct) do | ||||
rspamd_logger.debugm(N, task, "metatoken: %s = %s", mt.desc[i], tok) | |||||
lua_util.debugm(N, task, "metatoken: %s = %s", mt.desc[i], tok) | |||||
table.insert(metatokens, tok) | table.insert(metatokens, tok) | ||||
end | end | ||||
end | end |
if data and type(data) == 'string' then | if data and type(data) == 'string' then | ||||
-- Cached | -- Cached | ||||
if data ~= 'OK' then | if data ~= 'OK' then | ||||
rspamd_logger.debugm(N, task, 'got cached result for %s: %s', key, data) | |||||
lua_util.debugm(N, task, 'got cached result for %s: %s', key, data) | |||||
data = rspamd_str_split(data, '\x30') | data = rspamd_str_split(data, '\x30') | ||||
yield_result(task, rule, data) | yield_result(task, rule, data) | ||||
else | else | ||||
rspamd_logger.debugm(N, task, 'got cached result for %s: %s', key, data) | |||||
lua_util.debugm(N, task, 'got cached result for %s: %s', key, data) | |||||
end | end | ||||
else | else | ||||
if err then | if err then | ||||
rspamd_logger.errx(task, 'failed to save virus cache for %s -> "%s": %s', | rspamd_logger.errx(task, 'failed to save virus cache for %s -> "%s": %s', | ||||
to_save, key, err) | to_save, key, err) | ||||
else | else | ||||
rspamd_logger.debugm(N, task, 'saved cached result for %s: %s', key, to_save) | |||||
lua_util.debugm(N, task, 'saved cached result for %s: %s', key, to_save) | |||||
end | end | ||||
end | end | ||||
upstream:ok() | upstream:ok() | ||||
data = tostring(data) | data = tostring(data) | ||||
local cached | local cached | ||||
rspamd_logger.debugm(N, task, '%s [%s]: got reply: %s', rule['symbol'], rule['type'], data) | |||||
lua_util.debugm(N, task, '%s [%s]: got reply: %s', rule['symbol'], rule['type'], data) | |||||
if data == 'stream: OK' then | if data == 'stream: OK' then | ||||
cached = 'OK' | cached = 'OK' | ||||
if rule['log_clean'] then | if rule['log_clean'] then | ||||
rspamd_logger.infox(task, '%s [%s]: message is clean', rule['symbol'], rule['type']) | rspamd_logger.infox(task, '%s [%s]: message is clean', rule['symbol'], rule['type']) | ||||
else | else | ||||
rspamd_logger.debugm(N, task, '%s [%s]: message is clean', rule['symbol'], rule['type']) | |||||
lua_util.debugm(N, task, '%s [%s]: message is clean', rule['symbol'], rule['type']) | |||||
end | end | ||||
else | else | ||||
local vname = string.match(data, 'stream: (.+) FOUND') | local vname = string.match(data, 'stream: (.+) FOUND') | ||||
for virus,_ in pairs(vnames) do | for virus,_ in pairs(vnames) do | ||||
table.insert(vnames_reordered, virus) | table.insert(vnames_reordered, virus) | ||||
end | end | ||||
rspamd_logger.debugm(N, task, "%s: number of virus names found %s", rule['type'], #vnames_reordered) | |||||
lua_util.debugm(N, task, "%s: number of virus names found %s", rule['type'], #vnames_reordered) | |||||
if #vnames_reordered > 0 then | if #vnames_reordered > 0 then | ||||
local vname = {} | local vname = {} | ||||
for _,virus in ipairs(vnames_reordered) do | for _,virus in ipairs(vnames_reordered) do | ||||
local function savapi_scan2_cb(err, data, conn) | local function savapi_scan2_cb(err, data, conn) | ||||
local result = tostring(data) | local result = tostring(data) | ||||
rspamd_logger.debugm(N, task, "%s: got reply: %s", rule['type'], result) | |||||
lua_util.debugm(N, task, "%s: got reply: %s", rule['type'], result) | |||||
-- Terminal response - clean | -- Terminal response - clean | ||||
if string.find(result, '200') or string.find(result, '210') then | if string.find(result, '200') or string.find(result, '210') then | ||||
local function savapi_greet2_cb(err, data, conn) | local function savapi_greet2_cb(err, data, conn) | ||||
local result = tostring(data) | local result = tostring(data) | ||||
if string.find(result, '100 PRODUCT') then | if string.find(result, '100 PRODUCT') then | ||||
rspamd_logger.debugm(N, task, "%s: scanning file: %s", rule['type'], message_file) | |||||
lua_util.debugm(N, task, "%s: scanning file: %s", rule['type'], message_file) | |||||
conn:add_write(savapi_scan1_cb, {string.format('SCAN %s\n', message_file)}) | conn:add_write(savapi_scan1_cb, {string.format('SCAN %s\n', message_file)}) | ||||
else | else | ||||
rspamd_logger.errx(task, '%s: invalid product id %s', rule['type'], rule['product_id']) | rspamd_logger.errx(task, '%s: invalid product id %s', rule['type'], rule['product_id']) |
return (e1.i or 0) < (e2.i or 0) | return (e1.i or 0) < (e2.i or 0) | ||||
end) | end) | ||||
rspamd_logger.debugm(N, task, 'got %s arc sections', #cbdata.seals) | |||||
lua_util.debugm(N, task, 'got %s arc sections', #cbdata.seals) | |||||
-- Now check sanity of what we have | -- Now check sanity of what we have | ||||
if not arc_validate_seals(task, cbdata.seals, cbdata.sigs, | if not arc_validate_seals(task, cbdata.seals, cbdata.sigs, | ||||
local function arc_seal_cb(_, res, err, domain) | local function arc_seal_cb(_, res, err, domain) | ||||
cbdata.checked = cbdata.checked + 1 | cbdata.checked = cbdata.checked + 1 | ||||
rspamd_logger.debugm(N, task, 'checked arc seal: %s(%s), %s processed', | |||||
lua_util.debugm(N, task, 'checked arc seal: %s(%s), %s processed', | |||||
res, err, cbdata.checked) | res, err, cbdata.checked) | ||||
if not res then | if not res then | ||||
end | end | ||||
local function arc_signature_cb(_, res, err, domain) | local function arc_signature_cb(_, res, err, domain) | ||||
rspamd_logger.debugm(N, task, 'checked arc signature %s: %s(%s), %s processed', | |||||
lua_util.debugm(N, task, 'checked arc signature %s: %s(%s), %s processed', | |||||
domain, res, err, cbdata.checked) | domain, res, err, cbdata.checked) | ||||
if not res then | if not res then | ||||
cbdata.res = 'fail' | cbdata.res = 'fail' | ||||
table.insert(cbdata.errors, string.format('sig:%s:%s', sig.d or '', lerr)) | table.insert(cbdata.errors, string.format('sig:%s:%s', sig.d or '', lerr)) | ||||
cbdata.checked = cbdata.checked + 1 | cbdata.checked = cbdata.checked + 1 | ||||
rspamd_logger.debugm(N, task, 'checked arc seal %s: %s(%s), %s processed', | |||||
lua_util.debugm(N, task, 'checked arc seal %s: %s(%s), %s processed', | |||||
sig.d, ret, lerr, cbdata.checked) | sig.d, ret, lerr, cbdata.checked) | ||||
end | end | ||||
end, cbdata.seals) | end, cbdata.seals) | ||||
table.insert(cbdata.errors, string.format('sig:%s:%s', sig.d or '', err)) | table.insert(cbdata.errors, string.format('sig:%s:%s', sig.d or '', err)) | ||||
else | else | ||||
processed = processed + 1 | processed = processed + 1 | ||||
rspamd_logger.debugm(N, task, 'processed arc signature %s[%s]: %s(%s), %s processed', | |||||
lua_util.debugm(N, task, 'processed arc signature %s[%s]: %s(%s), %s processed', | |||||
sig.d, sig.i, ret, err, cbdata.checked) | sig.d, sig.i, ret, err, cbdata.checked) | ||||
end | end | ||||
local s = dkim_canonicalize('ARC-Authentication-Results', | local s = dkim_canonicalize('ARC-Authentication-Results', | ||||
arc_auth_results[i].value) | arc_auth_results[i].value) | ||||
sha_ctx:update(s) | sha_ctx:update(s) | ||||
rspamd_logger.debugm(N, task, 'update signature with header: %s', s) | |||||
lua_util.debugm(N, task, 'update signature with header: %s', s) | |||||
end | end | ||||
if arc_sigs[i] then | if arc_sigs[i] then | ||||
local s = dkim_canonicalize('ARC-Message-Signature', | local s = dkim_canonicalize('ARC-Message-Signature', | ||||
arc_sigs[i].raw_header) | arc_sigs[i].raw_header) | ||||
sha_ctx:update(s) | sha_ctx:update(s) | ||||
rspamd_logger.debugm(N, task, 'update signature with header: %s', s) | |||||
lua_util.debugm(N, task, 'update signature with header: %s', s) | |||||
end | end | ||||
if arc_seals[i] then | if arc_seals[i] then | ||||
local s = dkim_canonicalize('ARC-Seal', arc_seals[i].raw_header) | local s = dkim_canonicalize('ARC-Seal', arc_seals[i].raw_header) | ||||
sha_ctx:update(s) | sha_ctx:update(s) | ||||
rspamd_logger.debugm(N, task, 'update signature with header: %s', s) | |||||
lua_util.debugm(N, task, 'update signature with header: %s', s) | |||||
end | end | ||||
end | end | ||||
end | end | ||||
local s = dkim_canonicalize('ARC-Authentication-Results', | local s = dkim_canonicalize('ARC-Authentication-Results', | ||||
cur_auth_results) | cur_auth_results) | ||||
sha_ctx:update(s) | sha_ctx:update(s) | ||||
rspamd_logger.debugm(N, task, 'update signature with header: %s', s) | |||||
lua_util.debugm(N, task, 'update signature with header: %s', s) | |||||
s = dkim_canonicalize('ARC-Message-Signature', header) | s = dkim_canonicalize('ARC-Message-Signature', header) | ||||
sha_ctx:update(s) | sha_ctx:update(s) | ||||
rspamd_logger.debugm(N, task, 'update signature with header: %s', s) | |||||
lua_util.debugm(N, task, 'update signature with header: %s', s) | |||||
local cur_arc_seal = string.format('i=%d; s=%s; d=%s; t=%d; a=rsa-sha256; cv=%s; b=', | local cur_arc_seal = string.format('i=%d; s=%s; d=%s; t=%d; a=rsa-sha256; cv=%s; b=', | ||||
cur_idx, params.selector, params.domain, math.floor(rspamd_util.get_time()), params.arc_cv) | cur_idx, params.selector, params.domain, math.floor(rspamd_util.get_time()), params.arc_cv) | ||||
s = string.format('%s:%s', 'arc-seal', cur_arc_seal) | s = string.format('%s:%s', 'arc-seal', cur_arc_seal) | ||||
sha_ctx:update(s) | sha_ctx:update(s) | ||||
rspamd_logger.debugm(N, task, 'initial update signature with header: %s', s) | |||||
lua_util.debugm(N, task, 'initial update signature with header: %s', s) | |||||
local sig = rspamd_rsa.sign_memory(privkey, sha_ctx:bin()) | local sig = rspamd_rsa.sign_memory(privkey, sha_ctx:bin()) | ||||
cur_arc_seal = string.format('%s%s', cur_arc_seal, | cur_arc_seal = string.format('%s%s', cur_arc_seal, | ||||
local exists,err = rspamd_util.file_exists(p.key) | local exists,err = rspamd_util.file_exists(p.key) | ||||
if not exists then | if not exists then | ||||
if err and err == 'No such file or directory' then | if err and err == 'No such file or directory' then | ||||
rspamd_logger.debugm(N, task, 'cannot read key from %s: %s', p.key, err) | |||||
lua_util.debugm(N, task, 'cannot read key from %s: %s', p.key, err) | |||||
else | else | ||||
rspamd_logger.warnx(N, task, 'cannot read key from %s: %s', p.key, err) | rspamd_logger.warnx(N, task, 'cannot read key from %s: %s', p.key, err) | ||||
end | end |
for _,sym in ipairs(settings.stop_symbols) do | for _,sym in ipairs(settings.stop_symbols) do | ||||
if task:has_symbol(sym) then | if task:has_symbol(sym) then | ||||
rspamd_logger.debugm(N, task, 'skip collection as symbol %s has fired', sym) | |||||
lua_util.debugm(N, task, 'skip collection as symbol %s has fired', sym) | |||||
return | return | ||||
end | end | ||||
end | end | ||||
nrows = nrows + 1 | nrows = nrows + 1 | ||||
table.insert(data_rows, row) | table.insert(data_rows, row) | ||||
rspamd_logger.debugm(N, task, "add clickhouse row %s / %s", nrows, settings.limit) | |||||
lua_util.debugm(N, task, "add clickhouse row %s / %s", nrows, settings.limit) | |||||
if nrows > settings['limit'] then | if nrows > settings['limit'] then | ||||
clickhouse_send_data(task) | clickhouse_send_data(task) | ||||
end | end | ||||
local function do_remove_partition(ev_base, cfg, table_name, partition_id) | local function do_remove_partition(ev_base, cfg, table_name, partition_id) | ||||
rspamd_logger.debugm(N, rspamd_config, "removing partition %s.%s", table_name, partition_id) | |||||
lua_util.debugm(N, rspamd_config, "removing partition %s.%s", table_name, partition_id) | |||||
local upstream = settings.upstream:get_upstream_round_robin() | local upstream = settings.upstream:get_upstream_round_robin() | ||||
local remove_partition_sql = "ALTER TABLE ${table_name} ${remove_method} PARTITION ${partition_id}" | local remove_partition_sql = "ALTER TABLE ${table_name} ${remove_method} PARTITION ${partition_id}" | ||||
local remove_method = (settings.retention.method == 'drop') and 'DROP' or 'DETACH' | local remove_method = (settings.retention.method == 'drop') and 'DROP' or 'DETACH' | ||||
local last_ts | local last_ts | ||||
if err then | if err then | ||||
rspamd_logger.debugm(N, rspamd_config, 'Failed to open %s: %s', ts_file, err) | |||||
lua_util.debugm(N, rspamd_config, 'Failed to open %s: %s', ts_file, err) | |||||
else | else | ||||
last_ts = tonumber(f:read('*number')) | last_ts = tonumber(f:read('*number')) | ||||
f:close() | f:close() |
limitations under the License. | limitations under the License. | ||||
]]-- | ]]-- | ||||
local lutil = require "lua_util" | |||||
local lua_util = require "lua_util" | |||||
local rspamd_logger = require "rspamd_logger" | local rspamd_logger = require "rspamd_logger" | ||||
local dkim_sign_tools = require "lua_dkim_tools" | local dkim_sign_tools = require "lua_dkim_tools" | ||||
local rspamd_util = require "rspamd_util" | local rspamd_util = require "rspamd_util" | ||||
rspamd_logger.infox(task, "cannot make request to load DKIM key for %s: %s", | rspamd_logger.infox(task, "cannot make request to load DKIM key for %s: %s", | ||||
rk, err) | rk, err) | ||||
elseif type(data) ~= 'string' then | elseif type(data) ~= 'string' then | ||||
rspamd_logger.debugm(N, task, "missing DKIM key for %s", rk) | |||||
lua_util.debugm(N, task, "missing DKIM key for %s", rk) | |||||
else | else | ||||
p.rawkey = data | p.rawkey = data | ||||
do_sign() | do_sign() | ||||
end | end | ||||
else | else | ||||
if (p.key and p.selector) then | if (p.key and p.selector) then | ||||
p.key = lutil.template(p.key, {domain = p.domain, selector = p.selector}) | |||||
p.key = lua_util.template(p.key, { domain = p.domain, selector = p.selector}) | |||||
local exists,err = rspamd_util.file_exists(p.key) | local exists,err = rspamd_util.file_exists(p.key) | ||||
if not exists then | if not exists then | ||||
if err and err == 'No such file or directory' then | if err and err == 'No such file or directory' then | ||||
rspamd_logger.debugm(N, task, 'cannot read key from %s: %s', p.key, err) | |||||
lua_util.debugm(N, task, 'cannot read key from %s: %s', p.key, err) | |||||
else | else | ||||
rspamd_logger.warnx(N, task, 'cannot read key from %s: %s', p.key, err) | rspamd_logger.warnx(N, task, 'cannot read key from %s: %s', p.key, err) | ||||
end | end | ||||
end | end | ||||
if not (settings.use_redis or settings.path or settings.domain or settings.path_map or settings.selector_map) then | if not (settings.use_redis or settings.path or settings.domain or settings.path_map or settings.selector_map) then | ||||
rspamd_logger.infox(rspamd_config, 'mandatory parameters missing, disable dkim signing') | rspamd_logger.infox(rspamd_config, 'mandatory parameters missing, disable dkim signing') | ||||
lutil.disable_module(N, "config") | |||||
lua_util.disable_module(N, "config") | |||||
return | return | ||||
end | end | ||||
if settings.use_redis then | if settings.use_redis then | ||||
if not redis_params then | if not redis_params then | ||||
rspamd_logger.errx(rspamd_config, | rspamd_logger.errx(rspamd_config, | ||||
'no servers are specified, but module is configured to load keys from redis, disable dkim signing') | 'no servers are specified, but module is configured to load keys from redis, disable dkim signing') | ||||
lutil.disable_module(N, "redis") | |||||
lua_util.disable_module(N, "redis") | |||||
return | return | ||||
end | end | ||||
end | end |
local rspamd_url = require "rspamd_url" | local rspamd_url = require "rspamd_url" | ||||
local rspamd_util = require "rspamd_util" | local rspamd_util = require "rspamd_util" | ||||
local rspamd_redis = require "lua_redis" | local rspamd_redis = require "lua_redis" | ||||
local lua_util = require "lua_util" | |||||
local check_local = false | local check_local = false | ||||
local check_authed = false | local check_authed = false | ||||
local function dmarc_report_xml() | local function dmarc_report_xml() | ||||
local entries = {} | local entries = {} | ||||
report_id = string.format('%s.%d.%d', reporting_domain, report_start, report_end) | report_id = string.format('%s.%d.%d', reporting_domain, report_start, report_end) | ||||
rspamd_logger.debugm(N, rspamd_config, 'new report: %s', report_id) | |||||
lua_util.debugm(N, rspamd_config, 'new report: %s', report_id) | |||||
local actions = { | local actions = { | ||||
push = function(t) | push = function(t) | ||||
local data = t[1] | local data = t[1] | ||||
end | end | ||||
local time = rspamd_util.get_time() | local time = rspamd_util.get_time() | ||||
if not stamp then | if not stamp then | ||||
rspamd_logger.debugm(N, rspamd_config, 'No state found - sending reports immediately') | |||||
lua_util.debugm(N, rspamd_config, 'No state found - sending reports immediately') | |||||
schedule_regular_send() | schedule_regular_send() | ||||
send_reports(time) | send_reports(time) | ||||
return | return | ||||
end | end | ||||
local delta = stamp - time + INTERVAL | local delta = stamp - time + INTERVAL | ||||
if delta <= 0 then | if delta <= 0 then | ||||
rspamd_logger.debugm(N, rspamd_config, 'Last send is too old - sending reports immediately') | |||||
lua_util.debugm(N, rspamd_config, 'Last send is too old - sending reports immediately') | |||||
schedule_regular_send() | schedule_regular_send() | ||||
send_reports(time) | send_reports(time) | ||||
return | return | ||||
end | end | ||||
rspamd_logger.debugm(N, rspamd_config, 'Scheduling next send in %s seconds', delta) | |||||
lua_util.debugm(N, rspamd_config, 'Scheduling next send in %s seconds', delta) | |||||
schedule_intermediate_send(delta) | schedule_intermediate_send(delta) | ||||
end) | end) | ||||
end | end |
local rspamd_logger = require 'rspamd_logger' | local rspamd_logger = require 'rspamd_logger' | ||||
local rspamd_http = require "rspamd_http" | local rspamd_http = require "rspamd_http" | ||||
local rspamd_lua_utils = require "lua_util" | |||||
local lua_util = require "lua_util" | |||||
local util = require "rspamd_util" | local util = require "rspamd_util" | ||||
local ucl = require "ucl" | local ucl = require "ucl" | ||||
local hash = require "rspamd_cryptobox_hash" | local hash = require "rspamd_cryptobox_hash" | ||||
local bulk_json = table.concat(tbl, "\n") | local bulk_json = table.concat(tbl, "\n") | ||||
local function http_index_data_callback(err, code, body, _) | local function http_index_data_callback(err, code, body, _) | ||||
-- todo error handling we may store the rows it into redis and send it again late | -- todo error handling we may store the rows it into redis and send it again late | ||||
rspamd_logger.debugm(N, task, "After create data %1", body) | |||||
lua_util.debugm(N, task, "After create data %1", body) | |||||
if code ~= 200 then | if code ~= 200 then | ||||
rspamd_logger.infox(task, "cannot push data to elastic backend (%s): %s (%s)", | rspamd_logger.infox(task, "cannot push data to elastic backend (%s): %s (%s)", | ||||
push_url, err, code) | push_url, err, code) | ||||
local function elastic_collect(task) | local function elastic_collect(task) | ||||
if not enabled then return end | if not enabled then return end | ||||
if not settings.allow_local and rspamd_lua_utils.is_rspamc_or_controller(task) then return end | |||||
if not settings.allow_local and lua_util.is_rspamc_or_controller(task) then return end | |||||
local row = {['rspamd_meta'] = get_general_metadata(task), | local row = {['rspamd_meta'] = get_general_metadata(task), | ||||
['@timestamp'] = tostring(util.get_time() * 1000)} | ['@timestamp'] = tostring(util.get_time() * 1000)} | ||||
table.insert(rows, row) | table.insert(rows, row) | ||||
err, code, body) | err, code, body) | ||||
enabled = false | enabled = false | ||||
else | else | ||||
rspamd_logger.debugm(N, 'pushed kibana template: %s', body) | |||||
lua_util.debugm(N, 'pushed kibana template: %s', body) | |||||
end | end | ||||
end | end | ||||
template_url, err, code, body) | template_url, err, code, body) | ||||
enabled = false | enabled = false | ||||
else | else | ||||
rspamd_logger.debugm(N, 'pushed rspamd template: %s', body) | |||||
lua_util.debugm(N, 'pushed rspamd template: %s', body) | |||||
push_kibana_template() | push_kibana_template() | ||||
end | end | ||||
end | end | ||||
if not settings['server'] and not settings['servers'] then | if not settings['server'] and not settings['servers'] then | ||||
rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module') | rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module') | ||||
rspamd_lua_utils.disable_module(N, "config") | |||||
lua_util.disable_module(N, "config") | |||||
else | else | ||||
if settings.use_https then | if settings.use_https then | ||||
connect_prefix = 'https://' | connect_prefix = 'https://' | ||||
if not settings.upstream then | if not settings.upstream then | ||||
rspamd_logger.errx('cannot parse elastic address: %s', | rspamd_logger.errx('cannot parse elastic address: %s', | ||||
settings['server'] or settings['servers']) | settings['server'] or settings['servers']) | ||||
rspamd_lua_utils.disable_module(N, "config") | |||||
lua_util.disable_module(N, "config") | |||||
return | return | ||||
end | end | ||||
if not settings['template_file'] then | if not settings['template_file'] then | ||||
rspamd_logger.infox(rspamd_config, 'elastic template_file is required, disabling module') | rspamd_logger.infox(rspamd_config, 'elastic template_file is required, disabling module') | ||||
rspamd_lua_utils.disable_module(N, "config") | |||||
lua_util.disable_module(N, "config") | |||||
return | return | ||||
end | end | ||||
if not elastic_template then | if not elastic_template then | ||||
rspamd_logger.infox(rspamd_config, 'elastic unable to read %s, disabling module', | rspamd_logger.infox(rspamd_config, 'elastic unable to read %s, disabling module', | ||||
settings['template_file']) | settings['template_file']) | ||||
rspamd_lua_utils.disable_module(N, "config") | |||||
lua_util.disable_module(N, "config") | |||||
return | return | ||||
end | end | ||||
return | return | ||||
end | end | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local rspamd_regexp = require "rspamd_regexp" | local rspamd_regexp = require "rspamd_regexp" | ||||
local lua_util = require "lua_util" | local lua_util = require "lua_util" | ||||
local rspamc_local_helo = "rspamc.local" | local rspamc_local_helo = "rspamc.local" | ||||
local function check_host_cb_mx(_, to_resolve, results, err) | local function check_host_cb_mx(_, to_resolve, results, err) | ||||
task:inc_dns_req() | task:inc_dns_req() | ||||
if err and (err ~= 'requested record is not found' and err ~= 'no records with this name') then | if err and (err ~= 'requested record is not found' and err ~= 'no records with this name') then | ||||
rspamd_logger.debugm(N, task, 'error looking up %s: %s', to_resolve, err) | |||||
lua_util.debugm(N, task, 'error looking up %s: %s', to_resolve, err) | |||||
end | end | ||||
if not results then | if not results then | ||||
task:insert_result('HFILTER_' .. symbol_suffix .. '_NORES_A_OR_MX', 1.0, | task:insert_result('HFILTER_' .. symbol_suffix .. '_NORES_A_OR_MX', 1.0, |
end | end | ||||
return nil | return nil | ||||
end, data))) | end, data))) | ||||
rspamd_logger.debugm(N, task, 'decompress took %s ms', | |||||
lua_util.debugm(N, task, 'decompress took %s ms', | |||||
(rspamd_util:get_ticks() - t1) * 1000.0) | (rspamd_util:get_ticks() - t1) * 1000.0) | ||||
collectgarbage() | collectgarbage() | ||||
end | end | ||||
return false, nil | return false, nil | ||||
end | end | ||||
end, data)))) | end, data)))) | ||||
rspamd_logger.debugm(N, task, 'parse took %s ms', | |||||
lua_util.debugm(N, task, 'parse took %s ms', | |||||
(rspamd_util:get_ticks() - t1) * 1000.0) | (rspamd_util:get_ticks() - t1) * 1000.0) | ||||
collectgarbage() | collectgarbage() | ||||
t1 = rspamd_util:get_ticks() | t1 = rspamd_util:get_ticks() | ||||
end, data) | end, data) | ||||
reply.rows = data | reply.rows = data | ||||
conn:send_ucl(reply) | conn:send_ucl(reply) | ||||
rspamd_logger.debugm(N, task, 'process + sending took %s ms', | |||||
lua_util.debugm(N, task, 'process + sending took %s ms', | |||||
(rspamd_util:get_ticks() - t1) * 1000.0) | (rspamd_util:get_ticks() - t1) * 1000.0) | ||||
collectgarbage() | collectgarbage() | ||||
else | else |
-- IP score is a module that set ip score of specific ip, asn, country | -- IP score is a module that set ip score of specific ip, asn, country | ||||
local rspamd_logger = require "rspamd_logger" | local rspamd_logger = require "rspamd_logger" | ||||
local rspamd_util = require "rspamd_util" | local rspamd_util = require "rspamd_util" | ||||
local rspamd_lua_utils = require "lua_util" | |||||
local lua_util = require "lua_util" | |||||
-- Default settings | -- Default settings | ||||
local redis_params = nil | local redis_params = nil | ||||
-- Set score based on metric's action | -- Set score based on metric's action | ||||
local ip_score_set = function(task) | local ip_score_set = function(task) | ||||
if rspamd_lua_utils.is_rspamc_or_controller(task) then return end | |||||
if lua_util.is_rspamc_or_controller(task) then return end | |||||
local function new_score_set(score, old_score, old_total) | local function new_score_set(score, old_score, old_total) | ||||
local new_total | local new_total | ||||
if old_total == -1 or old_total ~= old_total then | if old_total == -1 or old_total ~= old_total then | ||||
ipnet_score,total_ipnet, | ipnet_score,total_ipnet, | ||||
ip_score, total_ip = pool:get_variable('ip_score', | ip_score, total_ip = pool:get_variable('ip_score', | ||||
'double,double,double,double,double,double,double,double') | 'double,double,double,double,double,double,double,double') | ||||
rspamd_logger.debugm(M, task, "raw scores: asn: %s, total_asn: %s, country: %s, total_country: %s, ipnet: %s, total_ipnet: %s, ip:%s, total_ip: %s", | |||||
lua_util.debugm(M, task, "raw scores: asn: %s, total_asn: %s, country: %s, total_country: %s, ipnet: %s, total_ipnet: %s, ip:%s, total_ip: %s", | |||||
asn_score,total_asn, | asn_score,total_asn, | ||||
country_score,total_country, | country_score,total_country, | ||||
ipnet_score,total_ipnet, | ipnet_score,total_ipnet, | ||||
country_score,total_country = new_score_set(score, country_score, total_country) | country_score,total_country = new_score_set(score, country_score, total_country) | ||||
ipnet_score,total_ipnet = new_score_set(score, ipnet_score, total_ipnet) | ipnet_score,total_ipnet = new_score_set(score, ipnet_score, total_ipnet) | ||||
ip_score,total_ip = new_score_set(score, ip_score, total_ip) | ip_score,total_ip = new_score_set(score, ip_score, total_ip) | ||||
rspamd_logger.debugm(M, task, "processed scores: asn: %s, total_asn: %s, country: %s, total_country: %s, ipnet: %s, total_ipnet: %s, ip:%s, total_ip: %s", | |||||
lua_util.debugm(M, task, "processed scores: asn: %s, total_asn: %s, country: %s, total_country: %s, ipnet: %s, total_ipnet: %s, ip:%s, total_ip: %s", | |||||
asn_score,total_asn, | asn_score,total_asn, | ||||
country_score,total_country, | country_score,total_country, | ||||
ipnet_score,total_ipnet, | ipnet_score,total_ipnet, | ||||
-- XXX: upstreams | -- XXX: upstreams | ||||
end | end | ||||
local function calculate_score(score) | local function calculate_score(score) | ||||
local parts = rspamd_lua_utils.rspamd_str_split(score, '|') | |||||
local parts = lua_util.rspamd_str_split(score, '|') | |||||
local rep = tonumber(parts[1]) | local rep = tonumber(parts[1]) | ||||
local total = tonumber(parts[2]) | local total = tonumber(parts[2]) | ||||
flags = 'empty', | flags = 'empty', | ||||
}) | }) | ||||
else | else | ||||
rspamd_lua_utils.disable_module(N, "redis") | |||||
lua_util.disable_module(N, "redis") | |||||
end | end |
-- A plugin that pushes metadata (or whole messages) to external services | -- A plugin that pushes metadata (or whole messages) to external services | ||||
local redis_params | local redis_params | ||||
local lutil = require "lua_util" | |||||
local lua_util = require "lua_util" | |||||
local rspamd_http = require "rspamd_http" | local rspamd_http = require "rspamd_http" | ||||
local rspamd_tcp = require "rspamd_tcp" | local rspamd_tcp = require "rspamd_tcp" | ||||
local rspamd_util = require "rspamd_util" | local rspamd_util = require "rspamd_util" | ||||
meta.mail_to = table.concat(display_emails, ', ') | meta.mail_to = table.concat(display_emails, ', ') | ||||
meta.our_message_id = rspamd_util.random_hex(12) .. '@rspamd' | meta.our_message_id = rspamd_util.random_hex(12) .. '@rspamd' | ||||
meta.date = rspamd_util.time_to_string(rspamd_util.get_time()) | meta.date = rspamd_util.time_to_string(rspamd_util.get_time()) | ||||
return lutil.template(rule.email_template or settings.email_template, meta), {mail_targets = mail_targets} | |||||
return lua_util.template(rule.email_template or settings.email_template, meta), { mail_targets = mail_targets} | |||||
end, | end, | ||||
json = function(task) | json = function(task) | ||||
return ucl.to_format(get_general_metadata(task), 'json-compact') | return ucl.to_format(get_general_metadata(task), 'json-compact') | ||||
return | return | ||||
end | end | ||||
elseif not next(settings.rules) then | elseif not next(settings.rules) then | ||||
rspamd_logger.debugm(N, rspamd_config, 'No rules enabled') | |||||
lua_util.debugm(N, rspamd_config, 'No rules enabled') | |||||
return | return | ||||
end | end | ||||
if not settings.rules or not next(settings.rules) then | if not settings.rules or not next(settings.rules) then | ||||
local selector = rule.selector or 'default' | local selector = rule.selector or 'default' | ||||
local selected = selectors[selector](task) | local selected = selectors[selector](task) | ||||
if selected then | if selected then | ||||
rspamd_logger.debugm(N, task, 'Message selected for processing') | |||||
lua_util.debugm(N, task, 'Message selected for processing') | |||||
local formatter = rule.formatter or 'default' | local formatter = rule.formatter or 'default' | ||||
local formatted, extra = formatters[formatter](task, rule) | local formatted, extra = formatters[formatter](task, rule) | ||||
if formatted then | if formatted then | ||||
pushers[rule.backend](task, formatted, rule, extra) | pushers[rule.backend](task, formatted, rule, extra) | ||||
else | else | ||||
rspamd_logger.debugm(N, task, 'Formatter [%s] returned non-truthy value [%s]', formatter, formatted) | |||||
lua_util.debugm(N, task, 'Formatter [%s] returned non-truthy value [%s]', formatter, formatted) | |||||
end | end | ||||
else | else | ||||
rspamd_logger.debugm(N, task, 'Selector [%s] returned non-truthy value [%s]', selector, selected) | |||||
lua_util.debugm(N, task, 'Selector [%s] returned non-truthy value [%s]', selector, selected) | |||||
end | end | ||||
end | end | ||||
end | end | ||||
if not next(settings.rules) then | if not next(settings.rules) then | ||||
rspamd_logger.errx(rspamd_config, 'No rules enabled') | rspamd_logger.errx(rspamd_config, 'No rules enabled') | ||||
lutil.disable_module(N, "config") | |||||
lua_util.disable_module(N, "config") | |||||
end | end | ||||
for k, r in pairs(settings.rules) do | for k, r in pairs(settings.rules) do | ||||
rspamd_config:register_symbol({ | rspamd_config:register_symbol({ |
local res,trace = rule['expression']:process_traced(task) | local res,trace = rule['expression']:process_traced(task) | ||||
if not res or res == 0 then | if not res or res == 0 then | ||||
rspamd_logger.debugm(N, task, 'condition is false for %s', rule['symbol']) | |||||
lua_util.debugm(N, task, 'condition is false for %s', rule['symbol']) | |||||
return | return | ||||
else | else | ||||
rspamd_logger.debugm(N, task, 'condition is true for %s: %s', rule['symbol'], | |||||
lua_util.debugm(N, task, 'condition is true for %s: %s', rule['symbol'], | |||||
trace) | trace) | ||||
end | end | ||||
end | end | ||||
local function process_atom(atom, task) | local function process_atom(atom, task) | ||||
local f_ret = task:has_symbol(atom) | local f_ret = task:has_symbol(atom) | ||||
rspamd_logger.debugm(N, rspamd_config, 'check for symbol %s: %s', atom, f_ret) | |||||
lua_util.debugm(N, rspamd_config, 'check for symbol %s: %s', atom, f_ret) | |||||
if f_ret then | if f_ret then | ||||
return 1 | return 1 | ||||
newrule['expression'] = expression | newrule['expression'] = expression | ||||
fun.each(function(v) | fun.each(function(v) | ||||
rspamd_logger.debugm(N, rspamd_config, 'add dependency %s -> %s', | |||||
lua_util.debugm(N, rspamd_config, 'add dependency %s -> %s', | |||||
newrule['symbol'], v) | newrule['symbol'], v) | ||||
rspamd_config:register_dependency(newrule['symbol'], v) | rspamd_config:register_dependency(newrule['symbol'], v) | ||||
end, atoms) | end, atoms) |
return | return | ||||
end | end | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local util = require "rspamd_util" | |||||
local lua_util = require "lua_util" | |||||
-- Phishing detection interface for selecting phished urls and inserting corresponding symbol | -- Phishing detection interface for selecting phished urls and inserting corresponding symbol | ||||
-- | -- | ||||
-- | -- | ||||
local openphish_hash | local openphish_hash | ||||
local generic_service_data = {} | local generic_service_data = {} | ||||
local openphish_data = {} | local openphish_data = {} | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local util = require "rspamd_util" | |||||
local opts = rspamd_config:get_all_opt(N) | local opts = rspamd_config:get_all_opt(N) | ||||
if not (opts and type(opts) == 'table') then | if not (opts and type(opts) == 'table') then | ||||
rspamd_logger.infox(rspamd_config, 'Module is unconfigured') | rspamd_logger.infox(rspamd_config, 'Module is unconfigured') | ||||
local weight = 1.0 | local weight = 1.0 | ||||
local spoofed,why = util.is_utf_spoofed(tld, ptld) | local spoofed,why = util.is_utf_spoofed(tld, ptld) | ||||
if spoofed then | if spoofed then | ||||
rspamd_logger.debugm(N, task, "confusable: %1 -> %2: %3", tld, ptld, why) | |||||
lua_util.debugm(N, task, "confusable: %1 -> %2: %3", tld, ptld, why) | |||||
weight = 1.0 | weight = 1.0 | ||||
else | else | ||||
local dist = util.levenshtein_distance(tld, ptld, 2) | local dist = util.levenshtein_distance(tld, ptld, 2) | ||||
if a1 ~= a2 then | if a1 ~= a2 then | ||||
weight = 1 | weight = 1 | ||||
rspamd_logger.debugm(N, task, "confusable: %1 -> %2: different characters", | |||||
lua_util.debugm(N, task, "confusable: %1 -> %2: different characters", | |||||
tld, ptld, why) | tld, ptld, why) | ||||
else | else | ||||
-- We have totally different strings in tld, so penalize it significantly | -- We have totally different strings in tld, so penalize it significantly | ||||
end | end | ||||
end | end | ||||
rspamd_logger.debugm(N, task, "distance: %1 -> %2: %3", tld, ptld, dist) | |||||
lua_util.debugm(N, task, "distance: %1 -> %2: %3", tld, ptld, dist) | |||||
end | end | ||||
local function found_in_map(map, furl, sweight) | local function found_in_map(map, furl, sweight) |
return | return | ||||
end | end | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local rspamd_util = require "rspamd_util" | |||||
local rspamd_lua_utils = require "lua_util" | |||||
local lua_redis = require "lua_redis" | |||||
local fun = require "fun" | |||||
local lua_maps = require "lua_maps" | |||||
local lua_util = require "lua_util" | |||||
local rspamd_hash = require "rspamd_cryptobox_hash" | |||||
-- A plugin that implements ratelimits using redis | -- A plugin that implements ratelimits using redis | ||||
local E = {} | local E = {} | ||||
return string.format('Ratelimit "%s" exceeded', limit_type) | return string.format('Ratelimit "%s" exceeded', limit_type) | ||||
end | end | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local rspamd_util = require "rspamd_util" | |||||
local rspamd_lua_utils = require "lua_util" | |||||
local lua_redis = require "lua_redis" | |||||
local fun = require "fun" | |||||
local lua_maps = require "lua_maps" | |||||
local lua_util = require "lua_util" | |||||
local rspamd_hash = require "rspamd_cryptobox_hash" | |||||
local function load_scripts(cfg, ev_base) | local function load_scripts(cfg, ev_base) | ||||
bucket_check_id = lua_redis.add_redis_script(bucket_check_script, redis_params) | bucket_check_id = lua_redis.add_redis_script(bucket_check_script, redis_params) | ||||
for pr,value in pairs(prefixes) do | for pr,value in pairs(prefixes) do | ||||
local bucket = value.bucket | local bucket = value.bucket | ||||
local rate = (bucket.rate) / 1000.0 -- Leak rate in messages/ms | local rate = (bucket.rate) / 1000.0 -- Leak rate in messages/ms | ||||
rspamd_logger.debugm(N, task, "check limit %s:%s -> %s (%s/%s)", | |||||
lua_util.debugm(N, task, "check limit %s:%s -> %s (%s/%s)", | |||||
value.name, pr, value.hash, bucket.burst, bucket.rate) | value.name, pr, value.hash, bucket.burst, bucket.rate) | ||||
lua_redis.exec_redis_script(bucket_check_id, | lua_redis.exec_redis_script(bucket_check_id, | ||||
{key = value.hash, task = task, is_write = true}, | {key = value.hash, task = task, is_write = true}, | ||||
if prefixes then | if prefixes then | ||||
if task:has_pre_result() then | if task:has_pre_result() then | ||||
-- Already rate limited/greylisted, do nothing | -- Already rate limited/greylisted, do nothing | ||||
rspamd_logger.debugm(N, task, 'pre-action has been set, do not update') | |||||
lua_util.debugm(N, task, 'pre-action has been set, do not update') | |||||
return | return | ||||
end | end | ||||
rspamd_logger.errx(task, 'cannot update rate bucket %s: %s', | rspamd_logger.errx(task, 'cannot update rate bucket %s: %s', | ||||
k, err) | k, err) | ||||
else | else | ||||
rspamd_logger.debugm(N, task, | |||||
lua_util.debugm(N, task, | |||||
"updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s", | "updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s", | ||||
v.name, k, v.hash, | v.name, k, v.hash, | ||||
bucket.burst, bucket.rate, | bucket.burst, bucket.rate, |
return | return | ||||
end | end | ||||
local hash = require 'rspamd_cryptobox_hash' | |||||
local rspamd_logger = require 'rspamd_logger' | |||||
local rspamd_util = require 'rspamd_util' | |||||
local fun = require 'fun' | |||||
local lua_util = require 'lua_util' | |||||
-- This plugin implements various types of RBL checks | -- This plugin implements various types of RBL checks | ||||
-- Documentation can be found here: | -- Documentation can be found here: | ||||
-- https://rspamd.com/doc/modules/rbl.html | -- https://rspamd.com/doc/modules/rbl.html | ||||
local rbls = {} | local rbls = {} | ||||
local local_exclusions = nil | local local_exclusions = nil | ||||
local hash = require 'rspamd_cryptobox_hash' | |||||
local rspamd_logger = require 'rspamd_logger' | |||||
local rspamd_util = require 'rspamd_util' | |||||
local fun = require 'fun' | |||||
local lua_util = require 'lua_util' | |||||
local default_monitored = '1.0.0.127' | local default_monitored = '1.0.0.127' | ||||
local symbols = { | local symbols = { | ||||
rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) | rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) | ||||
end | end | ||||
if not results then | if not results then | ||||
rspamd_logger.debugm(N, task, 'DNS RESPONSE: label=%1 results=%2 error=%3 rbl=%4', to_resolve, false, err, rule['rbls'][1]['symbol']) | |||||
lua_util.debugm(N, task, 'DNS RESPONSE: label=%1 results=%2 error=%3 rbl=%4', to_resolve, false, err, rule['rbls'][1]['symbol']) | |||||
return | return | ||||
else | else | ||||
rspamd_logger.debugm(N, task, 'DNS RESPONSE: label=%1 results=%2 error=%3 rbl=%4', to_resolve, true, err, rule['rbls'][1]['symbol']) | |||||
lua_util.debugm(N, task, 'DNS RESPONSE: label=%1 results=%2 error=%3 rbl=%4', to_resolve, true, err, rule['rbls'][1]['symbol']) | |||||
end | end | ||||
for _,rbl in ipairs(rule.rbls) do | for _,rbl in ipairs(rule.rbls) do | ||||
for _,result in pairs(results) do | for _,result in pairs(results) do | ||||
local ipstr = result:to_string() | local ipstr = result:to_string() | ||||
local foundrc | local foundrc | ||||
rspamd_logger.debugm(N, task, '%s DNS result %s', to_resolve, ipstr) | |||||
lua_util.debugm(N, task, '%s DNS result %s', to_resolve, ipstr) | |||||
for s,i in pairs(rbl['returncodes']) do | for s,i in pairs(rbl['returncodes']) do | ||||
if type(i) == 'string' then | if type(i) == 'string' then | ||||
if string.find(ipstr, '^' .. i .. '$') then | if string.find(ipstr, '^' .. i .. '$') then | ||||
local params = {} -- indexed by rbl name | local params = {} -- indexed by rbl name | ||||
local function gen_rbl_rule(to_resolve, rbl) | local function gen_rbl_rule(to_resolve, rbl) | ||||
rspamd_logger.debugm(N, task, 'DNS REQUEST: label=%1 rbl=%2', to_resolve, rbl['symbol']) | |||||
lua_util.debugm(N, task, 'DNS REQUEST: label=%1 rbl=%2', to_resolve, rbl['symbol']) | |||||
if not params[to_resolve] then | if not params[to_resolve] then | ||||
local nrule = { | local nrule = { | ||||
to_resolve = to_resolve, | to_resolve = to_resolve, |
return | return | ||||
end | end | ||||
local rspamd_logger = require 'rspamd_logger' | |||||
local hash = require 'rspamd_cryptobox_hash' | |||||
local lua_util = require 'lua_util' | |||||
local lua_redis = require 'lua_redis' | |||||
-- A plugin that implements replies check using redis | -- A plugin that implements replies check using redis | ||||
-- Default port for redis upstreams | -- Default port for redis upstreams | ||||
use_local = true, | use_local = true, | ||||
} | } | ||||
local rspamd_logger = require 'rspamd_logger' | |||||
local hash = require 'rspamd_cryptobox_hash' | |||||
local lua_util = require 'lua_util' | |||||
local lua_redis = require 'lua_redis' | |||||
local N = "replies" | local N = "replies" | ||||
local function make_key(goop) | local function make_key(goop) | ||||
-- If sender is unauthenticated return | -- If sender is unauthenticated return | ||||
local ip = task:get_ip() | local ip = task:get_ip() | ||||
if settings.use_auth and task:get_user() then | if settings.use_auth and task:get_user() then | ||||
rspamd_logger.debugm(N, task, 'sender is authenticated') | |||||
lua_util.debugm(N, task, 'sender is authenticated') | |||||
elseif settings.use_local and (ip and ip:is_local()) then | elseif settings.use_local and (ip and ip:is_local()) then | ||||
rspamd_logger.debugm(N, task, 'sender is from local network') | |||||
lua_util.debugm(N, task, 'sender is from local network') | |||||
else | else | ||||
return | return | ||||
end | end | ||||
end | end | ||||
-- Create hash of message-id and store to redis | -- Create hash of message-id and store to redis | ||||
local key = make_key(msg_id) | local key = make_key(msg_id) | ||||
rspamd_logger.debugm(N, task, 'storing message-id for replies check') | |||||
lua_util.debugm(N, task, 'storing message-id for replies check') | |||||
local ret = lua_redis.redis_make_request(task, | local ret = lua_redis.redis_make_request(task, | ||||
redis_params, -- connect params | redis_params, -- connect params | ||||
key, -- hash key | key, -- hash key |
local hash = require 'rspamd_cryptobox_hash' | local hash = require 'rspamd_cryptobox_hash' | ||||
local lua_redis = require "lua_redis" | local lua_redis = require "lua_redis" | ||||
local fun = require "fun" | local fun = require "fun" | ||||
local redis_params = nil | local redis_params = nil | ||||
local default_expiry = 864000 -- 10 day by default | local default_expiry = 864000 -- 10 day by default | ||||
local rep_accepted = 0.0 | local rep_accepted = 0.0 | ||||
local rep_rejected = 0.0 | local rep_rejected = 0.0 | ||||
rspamd_logger.debugm(N, task, 'dkim reputation tokens: %s', requests) | |||||
lua_util.debugm(N, task, 'dkim reputation tokens: %s', requests) | |||||
local function tokens_cb(err, token, values) | local function tokens_cb(err, token, values) | ||||
nchecked = nchecked + 1 | nchecked = nchecked + 1 | ||||
local cr = require "rspamd_cryptobox_hash" | local cr = require "rspamd_cryptobox_hash" | ||||
local hkey = cr.create(spf_record):base32():sub(1, 32) | local hkey = cr.create(spf_record):base32():sub(1, 32) | ||||
rspamd_logger.debugm(N, task, 'check spf record %s -> %s', spf_record, hkey) | |||||
lua_util.debugm(N, task, 'check spf record %s -> %s', spf_record, hkey) | |||||
local function tokens_cb(err, token, values) | local function tokens_cb(err, token, values) | ||||
if values then | if values then | ||||
local cr = require "rspamd_cryptobox_hash" | local cr = require "rspamd_cryptobox_hash" | ||||
local hkey = cr.create(spf_record):base32():sub(1, 32) | local hkey = cr.create(spf_record):base32():sub(1, 32) | ||||
rspamd_logger.debugm(N, task, 'set spf record %s -> %s = %s', | |||||
lua_util.debugm(N, task, 'set spf record %s -> %s = %s', | |||||
spf_record, hkey, token) | spf_record, hkey, token) | ||||
rule.backend.set_token(task, rule, hkey, token) | rule.backend.set_token(task, rule, hkey, token) | ||||
end | end | ||||
rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) | rspamd_logger.errx(task, 'error looking up %s: %s', to_resolve, err) | ||||
end | end | ||||
if not results then | if not results then | ||||
rspamd_logger.debugm(N, task, 'DNS RESPONSE: label=%1 results=%2 error=%3 list=%4', | |||||
lua_util.debugm(N, task, 'DNS RESPONSE: label=%1 results=%2 error=%3 list=%4', | |||||
to_resolve, false, err, rule.backend.config.list) | to_resolve, false, err, rule.backend.config.list) | ||||
else | else | ||||
rspamd_logger.debugm(N, task, 'DNS RESPONSE: label=%1 results=%2 error=%3 list=%4', | |||||
lua_util.debugm(N, task, 'DNS RESPONSE: label=%1 results=%2 error=%3 list=%4', | |||||
to_resolve, true, err, rule.backend.config.list) | to_resolve, true, err, rule.backend.config.list) | ||||
end | end | ||||
values[data[i]] = ndata | values[data[i]] = ndata | ||||
end | end | ||||
end | end | ||||
rspamd_logger.debugm(N, task, 'got values for key %s -> %s', | |||||
lua_util.debugm(N, task, 'got values for key %s -> %s', | |||||
key, values) | key, values) | ||||
continuation_cb(nil, key, values) | continuation_cb(nil, key, values) | ||||
else | else | ||||
table.insert(args, k) | table.insert(args, k) | ||||
table.insert(args, v) | table.insert(args, v) | ||||
end | end | ||||
rspamd_logger.debugm(N, task, 'set values for key %s -> %s', | |||||
lua_util.debugm(N, task, 'set values for key %s -> %s', | |||||
key, values) | key, values) | ||||
local ret = lua_redis.exec_redis_script(rule.backend.script_set, | local ret = lua_redis.exec_redis_script(rule.backend.script_set, | ||||
{task = task, is_write = true}, | {task = task, is_write = true}, |
local rspamd_logger = require "rspamd_logger" | local rspamd_logger = require "rspamd_logger" | ||||
local rspamd_maps = require "lua_maps" | local rspamd_maps = require "lua_maps" | ||||
local lua_squeeze = require "lua_squeeze_rules" | local lua_squeeze = require "lua_squeeze_rules" | ||||
local lua_util = require "lua_util" | |||||
local rspamd_ip = require "rspamd_ip" | |||||
local rspamd_regexp = require "rspamd_regexp" | |||||
local ucl = require "ucl" | |||||
local fun = require "fun" | |||||
local redis_params | local redis_params | ||||
local settings = {} | local settings = {} | ||||
local settings_ids = {} | local settings_ids = {} | ||||
local settings_initialized = false | local settings_initialized = false | ||||
local max_pri = 0 | local max_pri = 0 | ||||
local rspamd_ip = require "rspamd_ip" | |||||
local rspamd_regexp = require "rspamd_regexp" | |||||
local ucl = require "ucl" | |||||
local fun = require "fun" | |||||
local function apply_settings(task, to_apply) | local function apply_settings(task, to_apply) | ||||
task:set_settings(to_apply) | task:set_settings(to_apply) | ||||
end | end | ||||
if not key then | if not key then | ||||
rspamd_logger.debugm(N, task, 'handler number %s returned nil', id) | |||||
lua_util.debugm(N, task, 'handler number %s returned nil', id) | |||||
return | return | ||||
end | end | ||||
local function freemail_search(input) | local function freemail_search(input) | ||||
local res = 0 | local res = 0 | ||||
local function trie_callback(number, pos) | local function trie_callback(number, pos) | ||||
rspamd_logger.debugm(N, rspamd_config, 'Matched pattern %1 at pos %2', freemail_domains[number], pos) | |||||
lua_util.debugm(N, rspamd_config, 'Matched pattern %1 at pos %2', freemail_domains[number], pos) | |||||
res = res + 1 | res = res + 1 | ||||
end | end | ||||
local elts = split(line, '[^:]+') | local elts = split(line, '[^:]+') | ||||
arg = elts[2] | arg = elts[2] | ||||
rspamd_logger.debugm(N, rspamd_config, 'trying to parse SA function %1 with args %2', | |||||
lua_util.debugm(N, rspamd_config, 'trying to parse SA function %1 with args %2', | |||||
elts[1], elts[2]) | elts[1], elts[2]) | ||||
local substitutions = { | local substitutions = { | ||||
{'^exists:', | {'^exists:', | ||||
local function parse_score(words) | local function parse_score(words) | ||||
if #words == 3 then | if #words == 3 then | ||||
-- score rule <x> | -- score rule <x> | ||||
rspamd_logger.debugm(N, rspamd_config, 'found score for %1: %2', words[2], words[3]) | |||||
lua_util.debugm(N, rspamd_config, 'found score for %1: %2', words[2], words[3]) | |||||
return tonumber(words[3]) | return tonumber(words[3]) | ||||
elseif #words == 6 then | elseif #words == 6 then | ||||
-- score rule <x1> <x2> <x3> <x4> | -- score rule <x1> <x2> <x3> <x4> | ||||
-- we assume here that bayes and network are enabled and select <x4> | -- we assume here that bayes and network are enabled and select <x4> | ||||
rspamd_logger.debugm(N, rspamd_config, 'found score for %1: %2', words[2], words[6]) | |||||
lua_util.debugm(N, rspamd_config, 'found score for %1: %2', words[2], words[6]) | |||||
return tonumber(words[6]) | return tonumber(words[6]) | ||||
else | else | ||||
rspamd_logger.errx(rspamd_config, 'invalid score for %1', words[2]) | rspamd_logger.errx(rspamd_config, 'invalid score for %1', words[2]) | ||||
local res = atom_cb(task) | local res = atom_cb(task) | ||||
if not res then | if not res then | ||||
rspamd_logger.debugm(N, task, 'atom: %1, NULL result', atom) | |||||
lua_util.debugm(N, task, 'atom: %1, NULL result', atom) | |||||
elseif res > 0 then | elseif res > 0 then | ||||
rspamd_logger.debugm(N, task, 'atom: %1, result: %2', atom, res) | |||||
lua_util.debugm(N, task, 'atom: %1, result: %2', atom, res) | |||||
end | end | ||||
return res | return res | ||||
else | else | ||||
real_sym = symbols_replacements[atom] | real_sym = symbols_replacements[atom] | ||||
end | end | ||||
if task:has_symbol(real_sym) then | if task:has_symbol(real_sym) then | ||||
rspamd_logger.debugm(N, task, 'external atom: %1, result: 1', real_sym) | |||||
lua_util.debugm(N, task, 'external atom: %1, result: 1', real_sym) | |||||
return 1 | return 1 | ||||
end | end | ||||
rspamd_logger.debugm(N, task, 'external atom: %1, result: 0', real_sym) | |||||
lua_util.debugm(N, task, 'external atom: %1, result: 0', real_sym) | |||||
end | end | ||||
return 0 | return 0 | ||||
end | end | ||||
--rule['re'] = nil | --rule['re'] = nil | ||||
else | else | ||||
local old_max_hits = rule['re']:get_max_hits() | local old_max_hits = rule['re']:get_max_hits() | ||||
rspamd_logger.debugm(N, rspamd_config, 'replace %1 -> %2', r, nexpr) | |||||
lua_util.debugm(N, rspamd_config, 'replace %1 -> %2', r, nexpr) | |||||
rspamd_config:replace_regexp({ | rspamd_config:replace_regexp({ | ||||
old_re = rule['re'], | old_re = rule['re'], | ||||
new_re = nre | new_re = nre | ||||
if not external_deps[k][rspamd_symbol] then | if not external_deps[k][rspamd_symbol] then | ||||
rspamd_config:register_dependency(k, rspamd_symbol) | rspamd_config:register_dependency(k, rspamd_symbol) | ||||
external_deps[k][rspamd_symbol] = true | external_deps[k][rspamd_symbol] = true | ||||
rspamd_logger.debugm(N, rspamd_config, | |||||
lua_util.debugm(N, rspamd_config, | |||||
'atom %1 is a direct foreign dependency, ' .. | 'atom %1 is a direct foreign dependency, ' .. | ||||
'register dependency for %2 on %3', | 'register dependency for %2 on %3', | ||||
a, k, rspamd_symbol) | a, k, rspamd_symbol) | ||||
if not external_deps[k][dep] then | if not external_deps[k][dep] then | ||||
rspamd_config:register_dependency(k, dep) | rspamd_config:register_dependency(k, dep) | ||||
external_deps[k][dep] = true | external_deps[k][dep] = true | ||||
rspamd_logger.debugm(N, rspamd_config, | |||||
lua_util.debugm(N, rspamd_config, | |||||
'atom %1 is an indirect foreign dependency, ' .. | 'atom %1 is an indirect foreign dependency, ' .. | ||||
'register dependency for %2 on %3', | 'register dependency for %2 on %3', | ||||
a, k, dep) | a, k, dep) |
local redis_params | local redis_params | ||||
local use_redis = false; | local use_redis = false; | ||||
local M = 'spamtrap' | local M = 'spamtrap' | ||||
local lutil = require "lua_util" | |||||
local lua_util = require "lua_util" | |||||
local settings = { | local settings = { | ||||
symbol = 'SPAMTRAP', | symbol = 'SPAMTRAP', | ||||
rspamd_logger.infox(task, 'spamtrap found: <%s>', rcpt) | rspamd_logger.infox(task, 'spamtrap found: <%s>', rcpt) | ||||
if settings.smtp_message then | if settings.smtp_message then | ||||
task:set_pre_result(settings['action'], | task:set_pre_result(settings['action'], | ||||
lutil.template(settings.smtp_message, {rcpt = rcpt})) | |||||
lua_util.template(settings.smtp_message, { rcpt = rcpt})) | |||||
else | else | ||||
local smtp_message = 'unknown error' | local smtp_message = 'unknown error' | ||||
if settings.action == 'no action' then | if settings.action == 'no action' then | ||||
end | end | ||||
called_for_domain = true | called_for_domain = true | ||||
else | else | ||||
rspamd_logger.debugm(M, task, 'skip spamtrap for %s', target) | |||||
lua_util.debugm(M, task, 'skip spamtrap for %s', target) | |||||
end | end | ||||
end | end | ||||
end | end | ||||
if settings['map']:get_key(target) then | if settings['map']:get_key(target) then | ||||
do_action(target) | do_action(target) | ||||
else | else | ||||
rspamd_logger.debugm(M, task, 'skip spamtrap for %s', target) | |||||
lua_util.debugm(M, task, 'skip spamtrap for %s', target) | |||||
end | end | ||||
end | end | ||||
end | end |
local pattern_idx = pattern .. tostring(idx) .. type | local pattern_idx = pattern .. tostring(idx) .. type | ||||
if param['multi'] or not matched[pattern_idx] then | if param['multi'] or not matched[pattern_idx] then | ||||
rspamd_logger.debugm(N, task, "<%1> matched pattern %2 at pos %3", | |||||
lua_util.debugm(N, task, "<%1> matched pattern %2 at pos %3", | |||||
task:get_message_id(), pattern, pos) | task:get_message_id(), pattern, pos) | ||||
task:insert_result(param['symbol'], 1.0, type) | task:insert_result(param['symbol'], 1.0, type) | ||||
if not param['multi'] then | if not param['multi'] then |
return | return | ||||
end | end | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local rspamd_http = require "rspamd_http" | |||||
local hash = require "rspamd_cryptobox_hash" | |||||
local lua_util = require "lua_util" | |||||
-- Some popular UA | -- Some popular UA | ||||
local default_ua = { | local default_ua = { | ||||
'Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)', | 'Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)', | ||||
top_urls_count = 200, -- how many top urls to save | top_urls_count = 200, -- how many top urls to save | ||||
} | } | ||||
local rspamd_logger = require "rspamd_logger" | |||||
local rspamd_http = require "rspamd_http" | |||||
local hash = require "rspamd_cryptobox_hash" | |||||
local lua_util = require "lua_util" | |||||
local function cache_url(task, orig_url, url, key, param) | local function cache_url(task, orig_url, url, key, param) | ||||
local function redis_trim_cb(err, data) | local function redis_trim_cb(err, data) | ||||
if err then | if err then | ||||
if rspamd_plugins.surbl.is_redirector(task, loc) then | if rspamd_plugins.surbl.is_redirector(task, loc) then | ||||
resolve_cached(task, orig_url, loc, key, param, ntries + 1) | resolve_cached(task, orig_url, loc, key, param, ntries + 1) | ||||
else | else | ||||
rspamd_logger.debugm(N, task, | |||||
lua_util.debugm(N, task, | |||||
"stop resolving redirects as %s is not a redirector", loc) | "stop resolving redirects as %s is not a redirector", loc) | ||||
cache_url(task, orig_url, loc, key, param) | cache_url(task, orig_url, loc, key, param) | ||||
end | end |