-- Install in mailcow data/conf/rspamd/plugins.d/ only after the API and -- configuration have been validated against the deployed mailcow version. -- Checks the entire SMTP message, not individual attachment byte counts. local rspamd_http = require "rspamd_http" local ucl = require "ucl" local opts = rspamd_config:get_all_opt('official_mail_size') if not opts or type(opts.url) ~= 'string' or type(opts.token) ~= 'string' or #opts.token < 32 then return end local free_limit = 25 * 1024 * 1024 local function temporary_failure(task) -- Do not silently deliver an oversized message when the policy service is -- unreachable. A temporary SMTP failure allows the sender to retry. task:set_pre_result('soft reject', 'Temporary recipient size check failure', 'official_mail_size') end local function check_message(task) -- Submission from our own web/native clients is authenticated and may -- legitimately contain a 40 MiB ordinary attachment plus MIME overhead. if task:get_user() then return end local size = task:get_size() if not size or size <= free_limit then return end local smtp_recipients = task:get_recipients('smtp') if not smtp_recipients or #smtp_recipients == 0 then return end local recipients = {} for _, recipient in ipairs(smtp_recipients) do if type(recipient.addr) == 'string' then table.insert(recipients, recipient.addr) end end if #recipients == 0 then return end local ok = rspamd_http.request({ task = task, url = opts.url, method = 'POST', body = ucl.to_format({ sizeBytes = size, recipients = recipients }, 'json'), headers = { ['Content-Type'] = 'application/json', ['Authorization'] = 'Bearer ' .. opts.token, }, timeout = 5.0, max_size = 2048, callback = function(err, code, body) if err or code ~= 200 or not body then temporary_failure(task) return end local parser = ucl.parser() local parsed = parser:parse_string(body) local decision = parsed and parser:get_object() or nil if not decision or (decision.action ~= 'accept' and decision.action ~= 'reject') then temporary_failure(task) elseif decision.action == 'reject' then task:set_pre_result('reject', 'Recipient message size limit exceeded', 'official_mail_size') end end, }) if not ok then temporary_failure(task) end end rspamd_config:register_symbol({ name = 'OFFICIAL_MAIL_INBOUND_SIZE', type = 'prefilter', callback = check_message, score = 0.0, description = 'Official Mail recipient-level SMTP message size', })