123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- --[[
- Copyright (c) 2016, Andrew Lewis <nerf@judo.za.org>
- Copyright (c) 2017, Vsevolod Stakhov <vsevolod@highsecure.ru>
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- 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 = {}
-
- local E = {}
- local lua_util = require "lua_util"
- local rspamd_util = require "rspamd_util"
- local logger = require "rspamd_logger"
-
- local function check_violation(N, task, domain, selector)
- -- Check for DKIM_REJECT
- local sym_check = 'R_DKIM_REJECT'
-
- if N == 'arc' then sym_check = 'ARC_REJECT' end
- if task:has_symbol(sym_check) then
- local sym = task:get_symbol(sym_check)
- logger.infox(task, 'skip signing for %s:%s: violation %s found: %s',
- domain, selector, sym_check, sym.options)
- return false
- end
-
- return true
- end
-
- local function parse_dkim_http_headers(N, task, settings)
- -- Configure headers
- local headers = {
- sign_header = settings.http_sign_header or "PerformDkimSign",
- sign_on_reject_header = settings.http_sign_on_reject_header_header or 'SignOnAuthFailed',
- domain_header = settings.http_domain_header or 'DkimDomain',
- selector_header = settings.http_selector_header or 'DkimSelector',
- key_header = settings.http_key_header or 'DkimPrivateKey'
- }
-
- if task:get_request_header(headers.sign_header) then
- local domain = task:get_request_header(headers.domain_header)
- local selector = task:get_request_header(headers.selector_header)
- 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
-
- -- Now check if we need to check the existing auth
- local hdr = task:get_request_header(headers.sign_on_reject_header)
- if not hdr or tostring(hdr) == '0' or tostring(hdr) == 'false' then
- if not check_violation(N, task, domain, selector) then
- return false, {}
- end
- end
-
- return true,{
- rawkey = tostring(key),
- domain = tostring(domain),
- selector = tostring(selector)
- }
- end
-
- lua_util.debugm(N, task, 'no sign header %s', headers.sign_header)
- return false,{}
- end
-
- local function prepare_dkim_signing(N, task, settings)
- local is_local, is_sign_networks
-
- if settings.use_http_headers then
- return parse_dkim_http_headers(N, task, settings)
- end
-
- local auser = task:get_user()
- local ip = task:get_from_ip()
-
- if ip and ip:is_local() then
- is_local = true
- end
-
- if settings.auth_only and auser then
- lua_util.debugm(N, task, 'user is authenticated')
- elseif (settings.sign_networks and settings.sign_networks:get_key(ip)) then
- is_sign_networks = true
- lua_util.debugm(N, task, 'mail is from address in sign_networks')
- elseif settings.sign_local and is_local then
- lua_util.debugm(N, task, 'mail is from local address')
- elseif settings.sign_inbound and not is_local and not auser then
- lua_util.debugm(N, task, 'mail was sent to us')
- else
- lua_util.debugm(N, task, 'ignoring unauthenticated mail')
- return false,{}
- end
-
- local efrom = task:get_from('smtp')
- if not settings.allow_envfrom_empty and
- #(((efrom or E)[1] or E).addr or '') == 0 then
- lua_util.debugm(N, task, 'empty envelope from not allowed')
- return false,{}
- end
-
- local hfrom = task:get_from('mime')
- if not settings.allow_hdrfrom_multiple and (hfrom or E)[2] then
- lua_util.debugm(N, task, 'multiple header from not allowed')
- return false,{}
- end
-
- local eto = task:get_recipients(0)
-
- local dkim_domain
- local hdom = ((hfrom or E)[1] or E).domain
- local edom = ((efrom or E)[1] or E).domain
- local tdom = ((eto or E)[1] or E).domain
- local udom = string.match(auser or '', '.*@(.*)')
-
- local function get_dkim_domain(dtype)
- if settings[dtype] == 'header' then
- return hdom
- elseif settings[dtype] == 'envelope' then
- return edom
- elseif settings[dtype] == 'auth' then
- return udom
- elseif settings[dtype] == 'recipient' then
- return tdom
- end
- end
-
- if hdom then
- hdom = hdom:lower()
- end
- if edom then
- edom = edom:lower()
- end
- if udom then
- udom = udom:lower()
- end
- if tdom then
- tdom = tdom:lower()
- end
-
- 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)
- 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)
- 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)
- else
- dkim_domain = get_dkim_domain('use_domain')
- lua_util.debugm(N, task, 'use domain(%s) for signature: %s',
- settings.use_domain, dkim_domain)
- end
-
- if not dkim_domain then
- lua_util.debugm(N, task, 'could not extract dkim domain')
- return false,{}
- end
-
- if settings.use_esld then
- dkim_domain = rspamd_util.get_tld(dkim_domain)
- if hdom then
- hdom = rspamd_util.get_tld(hdom)
- end
- if edom then
- edom = rspamd_util.get_tld(edom)
- end
- end
-
- 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 settings.allow_hdrfrom_mismatch_local and is_local then
- 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
- lua_util.debugm(N, task, 'domain mismatch allowed for sign_networks: %1 != %2', hdom, edom)
- else
- lua_util.debugm(N, task, 'domain mismatch not allowed: %1 != %2', hdom, edom)
- return false,{}
- end
- end
-
- if auser and not settings.allow_username_mismatch then
- if not udom then
- lua_util.debugm(N, task, 'couldnt find domain in username')
- return false,{}
- end
- if settings.use_esld then
- udom = rspamd_util.get_tld(udom)
- end
- if udom ~= dkim_domain then
- lua_util.debugm(N, task, 'user domain mismatch')
- return false,{}
- end
- end
-
- local p = {}
-
- if settings.domain[dkim_domain] then
- p.selector = settings.domain[dkim_domain].selector
- p.key = settings.domain[dkim_domain].path
- end
-
- if not p.key and p.selector then
- local key_var = "dkim_key"
- local selector_var = "dkim_selector"
- if N == "arc" then
- key_var = "arc_key"
- selector_var = "arc_selector"
- end
-
- p.key = task:get_mempool():get_variable(key_var)
- local selector_override = task:get_mempool():get_variable(selector_var)
-
- if selector_override then
- p.selector = selector_override
- end
-
- if (not p.key or not p.selector) and (not (settings.try_fallback or
- settings.use_redis or settings.selector_map
- or settings.path_map)) then
- lua_util.debugm(N, task, 'dkim unconfigured and fallback disabled')
- return false,{}
- end
-
- lua_util.debugm(N, task, 'override selector and key to %s:%s', p.key, p.selector)
- end
-
- if not p.selector and settings.selector_map then
- local data = settings.selector_map:get_key(dkim_domain)
- if data then
- p.selector = data
- lua_util.debugm(N, task, 'override selector to "%s" using selector_map', p.selector)
- elseif not settings.try_fallback then
- lua_util.debugm(N, task, 'no selector for %s', dkim_domain)
- return false,{}
- end
- end
-
- if not p.key and settings.path_map then
- local data = settings.path_map:get_key(dkim_domain)
- if data then
- p.key = data
- lua_util.debugm(N, task, 'override key to "%s" using path_map', p.key)
- elseif not settings.try_fallback then
- lua_util.debugm(N, task, 'no key for %s', dkim_domain)
- return false,{}
- end
- end
-
- if not p.key then
- if not settings.use_redis then
- p.key = settings.path
- lua_util.debugm(N, task, 'use default key "%s" from path', p.key)
- end
- end
-
- if not p.selector then
- p.selector = settings.selector
- lua_util.debugm(N, task, 'use default selector "%s"', p.selector)
- end
-
- if settings.check_violation then
- if not check_violation(N, task, p.domain, p.selector) then
- return false,{}
- end
- end
-
- p.domain = dkim_domain
-
- return true,p
- end
-
- exports.prepare_dkim_signing = prepare_dkim_signing
-
- return exports
|