--[[ Copyright (c) 2011-2015, Vsevolod Stakhov All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ]]-- -- Module for checking mail list headers local symbol = 'MAILLIST' local rspamd_logger = require "rspamd_logger" -- EZMLM -- Mailing-List: .*run by ezmlm -- Precedence: bulk -- List-Post: -- List-Id: -- List-Unsubscribe: .* -- List-Archive: -- X-Mailman-Version: \d local function check_ml_mailman(task) -- Mailing-List local header = task:get_header('x-mailman-version') if not header or not string.find(header, '^%d') then return false end -- Precedence header = task:get_header('precedence') if not header or (not string.match(header, '^bulk$') and not string.match(header, '^list$')) then return false end -- For reminders we have other headers than for normal messages header = task:get_header('x-list-administrivia') local subject = task:get_header('subject') if (header and string.find(header, 'yes')) or (subject and string.find(subject, 'mailing list memberships reminder$')) then if not task:get_header('errors-to') or not task:get_header('x-beenthere') then return false end header = task:get_header('x-no-archive') if not header or not string.find(header, 'yes') then return false end return true end -- Other headers header = task:get_header('list-id') if not header then return false end header = task:get_header('list-post') if not header or not string.find(header, '^') then return false end header = task:get_header('list-unsubscribe') if not header or not string.find(header, '') then return false end return true end -- Subscribe.ru -- Precedence: normal -- List-Id: <.*.subscribe.ru> -- List-Help: -- List-Subscribe: -- List-Unsubscribe: -- List-Archive: -- List-Owner: -- List-Post: NO local function check_ml_subscriberu(task) -- List-Id local header = task:get_header('list-id') if not header or not string.find(header, '^<.*%.subscribe%.ru>$') then return false end -- Precedence header = task:get_header('precedence') if not header or not string.match(header, '^normal$') then return false end -- Other headers header = task:get_header('list-archive') if not header or not string.find(header, '^$') then return false end header = task:get_header('list-owner') if not header or not string.find(header, '^$') then return false end header = task:get_header('list-help') if not header or not string.find(header, '^$') then return false end -- Subscribe and unsubscribe header = task:get_header('list-subscribe') if not header or not string.find(header, '^$') then return false end header = task:get_header('list-unsubscribe') if not header or not string.find(header, '^$') then return false end return true end -- RFC 2369 headers local function check_rfc2369(task) local header = task:get_header('List-Id') if not header then return false end header = task:get_header('List-Unsubscribe') if not header or not string.find(header, '^^<.+>$') then return false end header = task:get_header('List-Subscribe') if not header or not string.find(header, '^^<.+>$') then return false end return true end -- RFC 2919 headers local function check_rfc2919(task) local header = task:get_header('List-Id') if not header or not string.find(header, '^<.+>$') then return false end return check_rfc2369(task) end -- Google groups detector -- header exists X-Google-Loop -- RFC 2919 headers exist -- local function check_ml_googlegroup(task) local header = task:get_header('X-Google-Loop') if not header then return false end return check_rfc2919(task) end -- Majordomo detector -- Check Sender for owner- or -owner -- Check Precendence for 'Bulk' or 'List' -- -- And nothing more can be extracted :( local function check_ml_majordomo(task) local header = task:get_header('Sender') if not header or (not string.find(header, '^owner-.*$') and not string.find(header, '^.*-owner$')) then return false end local header = task:get_header('Precedence') if not header or (header ~= 'list' and header ~= 'bulk') then return false end return true end -- CGP detector -- X-Listserver = CommuniGate Pro LIST -- RFC 2919 headers exist -- local function check_ml_cgp(task) local header = task:get_header('X-Listserver') if not header or header ~= 'CommuniGate Pro LIST' then return false end return check_rfc2919(task) end local function check_maillist(task) if check_ml_ezmlm(task) then task:insert_result(symbol, 1, 'ezmlm') elseif check_ml_mailman(task) then task:insert_result(symbol, 1, 'mailman') elseif check_ml_subscriberu(task) then task:insert_result(symbol, 1, 'subscribe.ru') elseif check_ml_googlegroup(task) then task:insert_result(symbol, 1, 'googlegroups') elseif check_ml_majordomo(task) then task:insert_result(symbol, 1, 'majordomo') elseif check_ml_cgp(task) then task:insert_result(symbol, 1, 'cgp') end end -- Registration if type(rspamd_config.get_api_version) ~= 'nil' then if rspamd_config:get_api_version() >= 1 then rspamd_config:register_module_option('maillist', 'symbol', 'string') end end -- Configuration local opts = rspamd_config:get_all_opt('maillist')if opts then if opts['symbol'] then symbol = opts['symbol'] rspamd_config:register_symbol(symbol, 1.0, check_maillist) end end