diff options
-rw-r--r-- | lualib/lua_dkim_tools.lua | 112 | ||||
-rw-r--r-- | src/fuzzy_storage.c | 14 | ||||
-rw-r--r-- | src/plugins/lua/arc.lua | 198 |
3 files changed, 197 insertions, 127 deletions
diff --git a/lualib/lua_dkim_tools.lua b/lualib/lua_dkim_tools.lua index b7f520fae..69c9462b5 100644 --- a/lualib/lua_dkim_tools.lua +++ b/lualib/lua_dkim_tools.lua @@ -13,7 +13,7 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -]]-- +]] -- local exports = {} @@ -33,7 +33,7 @@ local function check_violation(N, task, domain) if task:has_symbol(sym_check) then local sym = task:get_symbol(sym_check)[1] logger.infox(task, 'skip signing for %s: violation %s found: %s', - domain, sym_check, sym.options) + domain, sym_check, sym.options) return false end @@ -92,7 +92,6 @@ local function parse_dkim_http_headers(N, task, settings) local key = task:get_request_header(headers.key_header) if not (domain and selector and key) then - logger.errx(task, 'missing required headers to sign email') return false, {} end @@ -258,14 +257,14 @@ local function prepare_dkim_signing(N, task, settings) -- OpenDKIM style if is_skip_sign() then lua_util.debugm(N, task, - 'skip signing: is_sign_network: %s, is_authed: %s, is_local: %s', - is_sign_networks, is_authed, is_local) + 'skip signing: is_sign_network: %s, is_authed: %s, is_local: %s', + is_sign_networks, is_authed, is_local) return false, {} end if not hfrom or not hfrom[1] or not hfrom[1].addr then lua_util.debugm(N, task, - 'signing_table: cannot get data when no header from is presented') + 'signing_table: cannot get data when no header from is presented') return false, {} end local sign_entry = settings.signing_table:get_key(hfrom[1].addr:lower()) @@ -273,7 +272,7 @@ local function prepare_dkim_signing(N, task, settings) if sign_entry then -- Check opendkim style entries lua_util.debugm(N, task, - 'signing_table: found entry for %s: %s', hfrom[1].addr, sign_entry) + 'signing_table: found entry for %s: %s', hfrom[1].addr, sign_entry) if sign_entry == '%' then sign_entry = hdom end @@ -291,7 +290,7 @@ local function prepare_dkim_signing(N, task, settings) if not selector then logger.errx(task, 'no selector defined for sign_entry %s, key_entry %s', - sign_entry, key_entry) + sign_entry, key_entry) return false, {} end @@ -305,11 +304,11 @@ local function prepare_dkim_signing(N, task, settings) if st:sub(1, 1) == '/' or st == './' or st == '..' then res.key = parts[2]:gsub('%%', hdom) lua_util.debugm(N, task, 'perform dkim signing for %s, selector=%s, domain=%s, key file=%s', - hdom, selector, res.domain, res.key) + hdom, selector, res.domain, res.key) else res.rawkey = parts[2] -- No sanity check here lua_util.debugm(N, task, 'perform dkim signing for %s, selector=%s, domain=%s, raw key used', - hdom, selector, res.domain) + hdom, selector, res.domain) end return true, { res } @@ -327,56 +326,56 @@ local function prepare_dkim_signing(N, task, settings) if st:sub(1, 1) == '/' or st == './' or st == '..' then res.key = parts[3]:gsub('%%', hdom) lua_util.debugm(N, task, 'perform dkim signing for %s, selector=%s, domain=%s, key file=%s', - hdom, selector, res.domain, res.key) + hdom, selector, res.domain, res.key) else res.rawkey = parts[3] -- No sanity check here lua_util.debugm(N, task, 'perform dkim signing for %s, selector=%s, domain=%s, raw key used', - hdom, selector, res.domain) + hdom, selector, res.domain) end return true, { res } else logger.errx(task, 'invalid key entry for sign entry %s: %s; when signing %s domain', - sign_entry, key_entry, hdom) + sign_entry, key_entry, hdom) return false, {} end elseif settings.use_vault then -- Sign table is presented, the rest is covered by vault lua_util.debugm(N, task, 'check vault for %s, by sign entry %s, key entry is missing', - hdom, sign_entry) + hdom, sign_entry) return true, { domain = sign_entry, vault = true } else logger.errx(task, 'missing key entry for sign entry %s; when signing %s domain', - sign_entry, hdom) + sign_entry, hdom) return false, {} end else logger.errx(task, 'cannot get key entry for signing entry %s, when signing %s domain', - sign_entry, hdom) + sign_entry, hdom) return false, {} end else lua_util.debugm(N, task, - 'signing_table: no entry for %s', hfrom[1].addr) + 'signing_table: no entry for %s', hfrom[1].addr) return false, {} end else if settings.use_domain_sign_networks and is_sign_networks then dkim_domain = get_dkim_domain('use_domain_sign_networks') lua_util.debugm(N, task, - 'sign_networks: use domain(%s) for signature: %s', - settings.use_domain_sign_networks, dkim_domain) + 'sign_networks: use domain(%s) for signature: %s', + settings.use_domain_sign_networks, dkim_domain) elseif settings.use_domain_sign_local and is_local then dkim_domain = get_dkim_domain('use_domain_sign_local') 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 dkim_domain = get_dkim_domain('use_domain_sign_inbound') 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) elseif settings.use_domain_custom then if type(settings.use_domain_custom) == 'string' then -- Load custom function @@ -387,10 +386,10 @@ local function prepare_dkim_signing(N, task, settings) settings.use_domain_custom = res_or_err dkim_domain = settings.use_domain_custom(task) lua_util.debugm(N, task, 'use custom domain for signing: %s', - dkim_domain) + dkim_domain) else logger.errx(task, 'cannot load dkim domain custom script: invalid type: %s, expected function', - type(res_or_err)) + type(res_or_err)) settings.use_domain_custom = nil end else @@ -400,12 +399,12 @@ local function prepare_dkim_signing(N, task, settings) else dkim_domain = settings.use_domain_custom(task) lua_util.debugm(N, task, 'use custom domain for signing: %s', - dkim_domain) + dkim_domain) end else dkim_domain = get_dkim_domain('use_domain') lua_util.debugm(N, task, 'use domain(%s) for signature: %s', - settings.use_domain, dkim_domain) + settings.use_domain, dkim_domain) end end @@ -467,7 +466,7 @@ local function prepare_dkim_signing(N, task, settings) }) else lua_util.debugm(N, task, 'domain %s is not designated for vault', - dkim_domain) + dkim_domain) end else -- TODO: try every domain in the vault @@ -501,7 +500,7 @@ local function prepare_dkim_signing(N, task, settings) if ret then table.insert(p, k) lua_util.debugm(N, task, 'using mempool selector %s with key %s', - k.selector, k.key) + k.selector, k.key) end end @@ -530,11 +529,11 @@ local function prepare_dkim_signing(N, task, settings) if not settings.use_redis then insert_or_update_prop(N, task, p, 'key', - 'default path', settings.path) + 'default path', settings.path) end insert_or_update_prop(N, task, p, 'selector', - 'default selector', settings.selector) + 'default selector', settings.selector) if settings.check_violation then if not check_violation(N, task, p.domain) then @@ -543,7 +542,7 @@ local function prepare_dkim_signing(N, task, settings) end insert_or_update_prop(N, task, p, 'domain', 'dkim_domain', - dkim_domain) + dkim_domain) return #p > 0 and true or false, p end @@ -560,53 +559,53 @@ exports.sign_using_redis = function(N, task, settings, selectors, sign_func, err local function redis_key_cb(err, data) if err then err_func(string.format("cannot make request to load DKIM key for %s: %s", - rk, err)) + rk, err)) elseif type(data) ~= 'string' then lua_util.debugm(N, task, "missing DKIM key for %s", rk) else p.rawkey = data lua_util.debugm(N, task, 'found and parsed key for %s:%s in Redis', - p.domain, p.selector) + p.domain, p.selector) sign_func(task, p) end end local rret = lua_redis.redis_make_request(task, - settings.redis_params, -- connect params - rk, -- hash key - false, -- is write - redis_key_cb, --callback - 'HGET', -- command - { settings.key_prefix, rk } -- arguments + settings.redis_params, -- connect params + rk, -- hash key + false, -- is write + redis_key_cb, --callback + 'HGET', -- command + { settings.key_prefix, rk } -- arguments ) if not rret then err_func(task, - string.format("cannot make request to load DKIM key for %s", rk)) + string.format("cannot make request to load DKIM key for %s", rk)) end end for _, p in ipairs(selectors) do if settings.selector_prefix then logger.infox(task, "using selector prefix '%s' for domain '%s'", - settings.selector_prefix, p.domain); + settings.selector_prefix, p.domain); local function redis_selector_cb(err, data) if err or type(data) ~= 'string' then err_func(task, string.format("cannot make request to load DKIM selector for domain %s: %s", - p.domain, err)) + p.domain, err)) else try_redis_key(data, p) end end local rret = lua_redis.redis_make_request(task, - settings.redis_params, -- connect params - p.domain, -- hash key - false, -- is write - redis_selector_cb, --callback - 'HGET', -- command - { settings.selector_prefix, p.domain } -- arguments + settings.redis_params, -- connect params + p.domain, -- hash key + false, -- is write + redis_selector_cb, --callback + 'HGET', -- command + { settings.selector_prefix, p.domain } -- arguments ) if not rret then err_func(task, string.format("cannot make Redis request to load DKIM selector for domain %s", - p.domain)) + p.domain)) end else try_redis_key(p.selector, p) @@ -619,25 +618,25 @@ exports.sign_using_vault = function(N, task, settings, selector, sign_func, err_ local ucl = require "ucl" local full_url = string.format('%s/v1/%s/%s', - settings.vault_url, settings.vault_path or 'dkim', selector.domain) + settings.vault_url, settings.vault_path or 'dkim', selector.domain) local upstream_list = lua_util.http_upstreams_by_url(rspamd_config:get_mempool(), settings.vault_url) local function vault_callback(err, code, body, _) if code ~= 200 then err_func(task, string.format('cannot request data from the vault url: %s; %s (%s)', - full_url, err, body)) + full_url, err, body)) else local parser = ucl.parser() local res, parser_err = parser:parse_string(body) if not res then err_func(task, string.format('vault reply for %s (data=%s) cannot be parsed: %s', - full_url, body, parser_err)) + full_url, body, parser_err)) else local obj = parser:get_object() if not obj or not obj.data then err_func(task, string.format('vault reply for %s (data=%s) is invalid, no data', - full_url, body)) + full_url, body)) else local elts = obj.data.selectors or {} local errs = {} @@ -675,13 +674,13 @@ exports.sign_using_vault = function(N, task, settings, selector, sign_func, err_ alg = p.alg, } lua_util.debugm(N, task, 'found and parsed key for %s:%s in Vault', - dkim_sign_data.domain, dkim_sign_data.selector) + dkim_sign_data.domain, dkim_sign_data.selector) nvalid = nvalid + 1 sign_func(task, dkim_sign_data) end, fun.filter(is_selector_valid, elts)) for _, e in errs do lua_util.debugm(N, task, 'error found during processing Vault selectors: %s:%s', - e[1], e[2]) + e[1], e[2]) end if nvalid == 0 then @@ -707,7 +706,7 @@ exports.sign_using_vault = function(N, task, settings, selector, sign_func, err_ if not ret then err_func(task, string.format("cannot make HTTP request to load DKIM data domain %s", - selector.domain)) + selector.domain)) end end @@ -732,8 +731,7 @@ exports.process_signing_settings = function(N, settings, opts) selector_map = { 'map', 'DKIM selectors' }, signing_table = { 'glob', 'DKIM signing table' }, key_table = { 'glob', 'DKIM keys table' }, - vault_domains = { 'glob', 'DKIM signing domains in vault' }, - whitelisted_signers_map = { 'set', 'ARC trusted signers domains' } + vault_domains = { 'glob', 'DKIM signing domains in vault' } } for k, v in pairs(opts) do local maybe_map = maps_opts[k] diff --git a/src/fuzzy_storage.c b/src/fuzzy_storage.c index 58d123712..d6836df3b 100644 --- a/src/fuzzy_storage.c +++ b/src/fuzzy_storage.c @@ -1386,6 +1386,20 @@ rspamd_fuzzy_check_callback(struct rspamd_fuzzy_reply *result, void *ud) } } + /* Check if the returned hash from fuzzy matching should be skipped */ + if (session->ctx->skip_hashes && result->v1.value > 0) { + char hexbuf[sizeof(result->digest) * 2 + 1]; + rspamd_encode_hex_buf(result->digest, sizeof(result->digest), + hexbuf, sizeof(hexbuf) - 1); + hexbuf[sizeof(hexbuf) - 1] = '\0'; + + if (rspamd_match_hash_map(session->ctx->skip_hashes, + hexbuf, sizeof(hexbuf) - 1)) { + result->v1.value = 401; + result->v1.prob = 0.0f; + } + } + if (!isnan(session->ctx->delay) && rspamd_match_radix_map_addr(session->ctx->delay_whitelist, session->addr) == NULL) { diff --git a/src/plugins/lua/arc.lua b/src/plugins/lua/arc.lua index 45da1f5a2..954583ed0 100644 --- a/src/plugins/lua/arc.lua +++ b/src/plugins/lua/arc.lua @@ -72,12 +72,13 @@ local settings = { use_domain = 'header', use_esld = true, use_redis = false, - key_prefix = 'arc_keys', -- default hash name - reuse_auth_results = false, -- Reuse the existing authentication results + key_prefix = 'arc_keys', -- default hash name + reuse_auth_results = false, -- Reuse the existing authentication results whitelisted_signers_map = nil, -- Trusted signers domains - adjust_dmarc = true, -- Adjust DMARC rejected policy for trusted forwarders - allowed_ids = nil, -- Allowed settings id - forbidden_ids = nil, -- Banned settings id + whitelist = nil, -- Domains with broken ARC implementations to trust despite validation failures + adjust_dmarc = true, -- Adjust DMARC rejected policy for trusted forwarders + allowed_ids = nil, -- Allowed settings id + forbidden_ids = nil, -- Banned settings id } -- To match normal AR @@ -86,15 +87,15 @@ local ar_settings = lua_auth_results.default_settings local function parse_arc_header(hdr, target, is_aar) -- Split elements by ';' and trim spaces local arr = fun.totable(fun.map( - function(val) - return fun.totable(fun.map(lua_util.rspamd_str_trim, - fun.filter(function(v) - return v and #v > 0 - end, - lua_util.rspamd_str_split(val.decoded, ';') - ) - )) - end, hdr + function(val) + return fun.totable(fun.map(lua_util.rspamd_str_trim, + fun.filter(function(v) + return v and #v > 0 + end, + lua_util.rspamd_str_split(val.decoded, ';') + ) + )) + end, hdr )) -- v[1] is the key and v[2] is the value @@ -115,11 +116,11 @@ local function parse_arc_header(hdr, target, is_aar) if not is_aar then -- For normal ARC headers we split by kv pair, like k=v fun.each(function(v) - fill_arc_header_table(v, target[i]) - end, - fun.map(function(elt) - return lua_util.rspamd_str_split(elt, '=') - end, elts) + fill_arc_header_table(v, target[i]) + end, + fun.map(function(elt) + return lua_util.rspamd_str_split(elt, '=') + end, elts) ) else -- For AAR we check special case of i=%d and pass everything else to @@ -156,14 +157,14 @@ local function arc_validate_seals(task, seals, sigs, seal_headers, sig_headers) for i = 1, #seals do if (sigs[i].i or 0) ~= i then fail_reason = string.format('bad i for signature: %d, expected %d; d=%s', - sigs[i].i, i, sigs[i].d) + sigs[i].i, i, sigs[i].d) rspamd_logger.infox(task, fail_reason) task:insert_result(arc_symbols['invalid'], 1.0, fail_reason) return false, fail_reason end if (seals[i].i or 0) ~= i then fail_reason = string.format('bad i for seal: %d, expected %d; d=%s', - seals[i].i, i, seals[i].d) + seals[i].i, i, seals[i].d) rspamd_logger.infox(task, fail_reason) task:insert_result(arc_symbols['invalid'], 1.0, fail_reason) return false, fail_reason @@ -207,7 +208,7 @@ local function arc_callback(task) if #arc_sig_headers ~= #arc_seal_headers then -- We mandate that count of seals is equal to count of signatures rspamd_logger.infox(task, 'number of seals (%s) is not equal to number of signatures (%s)', - #arc_seal_headers, #arc_sig_headers) + #arc_seal_headers, #arc_sig_headers) task:insert_result(arc_symbols['invalid'], 1.0, 'invalid count of seals and signatures') return end @@ -249,7 +250,7 @@ local function arc_callback(task) -- Now check sanity of what we have local valid, validation_error = arc_validate_seals(task, cbdata.seals, cbdata.sigs, - arc_seal_headers, arc_sig_headers) + arc_seal_headers, arc_sig_headers) if not valid then task:cache_set('arc-failure', validation_error) return @@ -267,12 +268,20 @@ local function arc_callback(task) local function gen_arc_seal_cb(index, sig) return function(_, res, err, domain) lua_util.debugm(N, task, 'checked arc seal: %s(%s), %s processed', - res, err, index) + res, err, index) if not res then - cbdata.res = 'fail' - if err and domain then - table.insert(cbdata.errors, string.format('sig:%s:%s', domain, err)) + -- Check if this domain is whitelisted for broken ARC implementations + if settings.whitelist and domain and settings.whitelist:get_key(domain) then + rspamd_logger.infox(task, 'ARC seal validation failed for whitelisted domain %s, treating as valid: %s', + domain, err) + lua_util.debugm(N, task, 'whitelisted domain %s ARC seal failure ignored', domain) + res = true -- Treat as valid to continue the chain + else + cbdata.res = 'fail' + if err and domain then + table.insert(cbdata.errors, string.format('sig:%s:%s', domain, err)) + end end end @@ -283,7 +292,7 @@ local function arc_callback(task) local cur_aar = cbdata.ars[index] if not cur_aar then rspamd_logger.warnx(task, "cannot find Arc-Authentication-Results for trusted " .. - "forwarder %s on i=%s", domain, cbdata.index) + "forwarder %s on i=%s", domain, cbdata.index) else task:cache_set(AR_TRUSTED_CACHE_KEY, cur_aar) local seen_dmarc @@ -309,20 +318,20 @@ local function arc_callback(task) end end task:insert_result(arc_symbols.trusted_allow, mult, - string.format('%s:s=%s:i=%d', domain, sig.s, index)) + string.format('%s:s=%s:i=%d', domain, sig.s, index)) end end if index == #arc_sig_headers then if cbdata.res == 'success' then local arc_allow_result = string.format('%s:s=%s:i=%d', - domain, sig.s, index) + domain, sig.s, index) task:insert_result(arc_symbols.allow, 1.0, arc_allow_result) task:cache_set('arc-allow', arc_allow_result) else task:insert_result(arc_symbols.reject, 1.0, - rspamd_logger.slog('seal check failed: %s, %s', cbdata.res, - cbdata.errors)) + rspamd_logger.slog('seal check failed: %s, %s', cbdata.res, + cbdata.errors)) end end end @@ -330,12 +339,20 @@ local function arc_callback(task) local function arc_signature_cb(_, res, err, domain) lua_util.debugm(N, task, 'checked arc signature %s: %s(%s)', - domain, res, err) + domain, res, err) if not res then - cbdata.res = 'fail' - if err and domain then - table.insert(cbdata.errors, string.format('sig:%s:%s', domain, err)) + -- Check if this domain is whitelisted for broken ARC implementations + if settings.whitelist and domain and settings.whitelist:get_key(domain) then + rspamd_logger.infox(task, 'ARC signature validation failed for whitelisted domain %s, treating as valid: %s', + domain, err) + lua_util.debugm(N, task, 'whitelisted domain %s ARC signature failure ignored', domain) + res = true -- Treat as valid to continue the chain + else + cbdata.res = 'fail' + if err and domain then + table.insert(cbdata.errors, string.format('sig:%s:%s', domain, err)) + end end end if cbdata.res == 'success' then @@ -343,17 +360,24 @@ local function arc_callback(task) for i, sig in ipairs(cbdata.seals) do local ret, lerr = dkim_verify(task, sig.header, gen_arc_seal_cb(i, sig), 'arc-seal') if not ret then - cbdata.res = 'fail' - table.insert(cbdata.errors, string.format('seal:%s:s=%s:i=%s:%s', + -- Check if this domain is whitelisted for broken ARC implementations + if settings.whitelist and sig.d and settings.whitelist:get_key(sig.d) then + rspamd_logger.infox(task, 'ARC seal dkim_verify failed for whitelisted domain %s, treating as valid: %s', + sig.d, lerr) + lua_util.debugm(N, task, 'whitelisted domain %s ARC seal dkim_verify failure ignored', sig.d) + else + cbdata.res = 'fail' + table.insert(cbdata.errors, string.format('seal:%s:s=%s:i=%s:%s', sig.d or '', sig.s or '', sig.i or '', lerr)) - lua_util.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, i) + end end end else task:insert_result(arc_symbols['reject'], 1.0, - rspamd_logger.slog('signature check failed: %s, %s', cbdata.res, - cbdata.errors)) + rspamd_logger.slog('signature check failed: %s, %s', cbdata.res, + cbdata.errors)) end end @@ -397,25 +421,33 @@ local function arc_callback(task) is "fail" and the algorithm stops here. 9. If the algorithm reaches this step, then the Chain Validation Status is "pass", and the algorithm is complete. - ]]-- + ]] -- local processed = 0 local sig = cbdata.sigs[#cbdata.sigs] -- last AMS local ret, err = dkim_verify(task, sig.header, arc_signature_cb, 'arc-sign') if not ret then - cbdata.res = 'fail' - table.insert(cbdata.errors, string.format('sig:%s:%s', sig.d or '', err)) + -- Check if this domain is whitelisted for broken ARC implementations + if settings.whitelist and sig.d and settings.whitelist:get_key(sig.d) then + rspamd_logger.infox(task, 'ARC signature dkim_verify failed for whitelisted domain %s, treating as valid: %s', + sig.d, err) + lua_util.debugm(N, task, 'whitelisted domain %s ARC signature dkim_verify failure ignored', sig.d) + processed = processed + 1 + else + cbdata.res = 'fail' + table.insert(cbdata.errors, string.format('sig:%s:%s', sig.d or '', err)) + end else processed = processed + 1 lua_util.debugm(N, task, 'processed arc signature %s[%s]: %s(%s), %s total', - sig.d, sig.i, ret, err, #cbdata.seals) + sig.d, sig.i, ret, err, #cbdata.seals) end if processed == 0 then task:insert_result(arc_symbols['reject'], 1.0, - rspamd_logger.slog('cannot verify %s of %s signatures: %s', - #arc_sig_headers - processed, #arc_sig_headers, cbdata.errors)) + rspamd_logger.slog('cannot verify %s of %s signatures: %s', + #arc_sig_headers - processed, #arc_sig_headers, cbdata.errors)) end end @@ -538,13 +570,13 @@ local function arc_sign_seal(task, params, header) for i = 1, #arc_seals, 1 do if arc_auth_results[i] then local s = dkim_canonicalize('ARC-Authentication-Results', - arc_auth_results[i].raw_header) + arc_auth_results[i].raw_header) sha_ctx:update(s) lua_util.debugm(N, task, 'update signature with header: %s', s) end if arc_sigs[i] then local s = dkim_canonicalize('ARC-Message-Signature', - arc_sigs[i].raw_header) + arc_sigs[i].raw_header) sha_ctx:update(s) lua_util.debugm(N, task, 'update signature with header: %s', s) end @@ -557,16 +589,16 @@ local function arc_sign_seal(task, params, header) end header = lua_util.fold_header(task, - 'ARC-Message-Signature', - header) + 'ARC-Message-Signature', + header) cur_auth_results = string.format('i=%d; %s', cur_idx, cur_auth_results) cur_auth_results = lua_util.fold_header(task, - 'ARC-Authentication-Results', - cur_auth_results, ';') + 'ARC-Authentication-Results', + cur_auth_results, ';') local s = dkim_canonicalize('ARC-Authentication-Results', - cur_auth_results) + cur_auth_results) sha_ctx:update(s) lua_util.debugm(N, task, 'update signature with header: %s', s) s = dkim_canonicalize('ARC-Message-Signature', header) @@ -574,10 +606,10 @@ local function arc_sign_seal(task, params, header) 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=', - 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) sha_ctx:update(s) lua_util.debugm(N, task, 'initial update signature with header: %s', s) @@ -591,20 +623,23 @@ local function arc_sign_seal(task, params, header) local sig = rspamd_rsa.sign_memory(privkey, sha_ctx:bin()) cur_arc_seal = string.format('%s%s', cur_arc_seal, - sig:base64(70, nl_type)) + sig:base64(70, nl_type)) lua_mime.modify_headers(task, { add = { ['ARC-Authentication-Results'] = { order = 1, value = cur_auth_results }, ['ARC-Message-Signature'] = { order = 1, value = header }, - ['ARC-Seal'] = { order = 1, value = lua_util.fold_header(task, - 'ARC-Seal', cur_arc_seal) } + ['ARC-Seal'] = { + order = 1, + value = lua_util.fold_header(task, + 'ARC-Seal', cur_arc_seal) + } }, -- RFC requires a strict order for these headers to be inserted order = { 'ARC-Authentication-Results', 'ARC-Message-Signature', 'ARC-Seal' }, }) task:insert_result(settings.sign_symbol, 1.0, - string.format('%s:s=%s:i=%d', params.domain, params.selector, cur_idx)) + string.format('%s:s=%s:i=%d', params.domain, params.selector, cur_idx)) end local function prepare_arc_selector(task, sel) @@ -668,7 +703,6 @@ local function prepare_arc_selector(task, sel) else default_arc_cv() end - end return true @@ -696,18 +730,17 @@ local function do_sign(task, sign_params) sign_params.strict_pubkey_check = not settings.allow_pubkey_mismatch elseif not settings.allow_pubkey_mismatch then rspamd_logger.errx(task, 'public key for domain %s/%s is not found: %s, skip signing', - sign_params.domain, sign_params.selector, err) + sign_params.domain, sign_params.selector, err) return else rspamd_logger.infox(task, 'public key for domain %s/%s is not found: %s', - sign_params.domain, sign_params.selector, err) + sign_params.domain, sign_params.selector, err) end local dret, hdr = dkim_sign(task, sign_params) if dret then arc_sign_seal(task, sign_params, hdr) end - end, forced = true }) @@ -768,6 +801,31 @@ end dkim_sign_tools.process_signing_settings(N, settings, opts) +-- Process ARC-specific maps that aren't handled by dkim_sign_tools +local lua_maps = require "lua_maps" + +if opts.whitelisted_signers_map then + settings.whitelisted_signers_map = lua_maps.map_add_from_ucl(opts.whitelisted_signers_map, 'set', + 'ARC trusted signers domains') + if not settings.whitelisted_signers_map then + rspamd_logger.errx(rspamd_config, 'cannot load whitelisted_signers_map') + settings.whitelisted_signers_map = nil + else + rspamd_logger.infox(rspamd_config, 'loaded ARC whitelisted signers map') + end +end + +if opts.whitelist then + settings.whitelist = lua_maps.map_add_from_ucl(opts.whitelist, 'set', + 'ARC domains with broken implementations') + if not settings.whitelist then + rspamd_logger.errx(rspamd_config, 'cannot load ARC whitelist map') + settings.whitelist = nil + else + rspamd_logger.infox(rspamd_config, 'loaded ARC whitelist map') + end +end + if not dkim_sign_tools.validate_signing_settings(settings) then rspamd_logger.infox(rspamd_config, 'mandatory parameters missing, disable arc signing') return @@ -780,7 +838,7 @@ if ar_opts and ar_opts.routines then if routines['authentication-results'] then ar_settings = lua_util.override_defaults(ar_settings, - routines['authentication-results']) + routines['authentication-results']) end end @@ -789,7 +847,7 @@ if settings.use_redis then if not redis_params then rspamd_logger.errx(rspamd_config, 'no servers are specified, ' .. - 'but module is configured to load keys from redis, disable arc signing') + 'but module is configured to load keys from redis, disable arc signing') return end @@ -845,9 +903,9 @@ if settings.adjust_dmarc and settings.whitelisted_signers_map then local dmarc_fwd = ar.dmarc if dmarc_fwd == 'pass' then rspamd_logger.infox(task, "adjust dmarc reject score as trusted forwarder " - .. "proved DMARC validity for %s", ar['header.from']) + .. "proved DMARC validity for %s", ar['header.from']) task:adjust_result(sym_to_adjust, 0.1, - 'ARC trusted') + 'ARC trusted') end end end |