-- Module for checking mail list headers local symbol = 'MAILLIST' -- EZMLM -- Mailing-List: .*run by ezmlm -- Precedence: bulk -- List-Post: -- List-Id: -- List-Unsubscribe: .* -- List-Archive: -- X-Mailman-Version: \d function check_ml_mailman(task) local message = task:get_message() -- Mailing-List local header = message:get_header('x-mailman-version') if not header or not string.find(header[1], '^%d') then return false end -- Precedence header = message:get_header('precedence') if not header or (not string.match(header[1], '^bulk$') and not string.match(header[1], '^list$')) then return false end -- For reminders we have other headers than for normal messages header = message:get_header('x-list-administrivia') local subject = message:get_header('subject') if (header and string.find(header[1], 'yes')) or (subject and string.find(subject[1], 'mailing list memberships reminder$')) then if not message:get_header('errors-to') or not message:get_header('x-beenthere') then return false end header = message:get_header('x-no-archive') if not header or not string.find(header[1], 'yes') then return false end return true end -- Other headers header = message:get_header('list-id') if not header then return false end header = message:get_header('list-post') if not header or not string.find(header[1], '^') then return false end header = message:get_header('list-unsubscribe') if not header or not string.find(header[1], '') 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 function check_ml_subscriberu(task) local message = task:get_message() -- List-Id local header = message:get_header('list-id') if not header or not string.find(header[1], '^<.*%.subscribe%.ru>$') then return false end -- Precedence header = message:get_header('precedence') if not header or not string.match(header[1], '^normal$') then return false end -- Other headers header = message:get_header('list-archive') if not header or not string.find(header[1], '^$') then return false end header = message:get_header('list-owner') if not header or not string.find(header[1], '^$') then return false end header = message:get_header('list-help') if not header or not string.find(header[1], '^$') then return false end -- Subscribe and unsubscribe header = message:get_header('list-subscribe') if not header or not string.find(header[1], '^$') then return false end header = message:get_header('list-unsubscribe') if not header or not string.find(header[1], '^$') then return false end return true end -- RFC 2369 headers function check_rfc2369(task) local message = task:get_message() local header = message:get_header('List-Id') if not header then return false end header = message:get_header('List-Unsubscribe') if not header or not string.find(header[1], '^^<.+>$') then return false end header = message:get_header('List-Subscribe') if not header or not string.find(header[1], '^^<.+>$') then return false end return true end -- RFC 2919 headers function check_rfc2919(task) local message = task:get_message() local header = message:get_header('List-Id') if not header or not string.find(header[1], '^<.+>$') then return false end return check_rfc2369(task) end -- Google groups detector -- header exists X-Google-Loop -- RFC 2919 headers exist -- function check_ml_googlegroup(task) local message = task:get_message() local header = message: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 :( function check_ml_majordomo(task) local message = task:get_message() local header = message:get_header('Sender') if not header or (not string.find(header[1], '^owner-.*$') and not string.find(header[1], '^.*-owner$')) then return false end local header = message:get_header('Precedence') if not header or (header[1] ~= 'list' and header[1] ~= 'bulk') then return false end return true end -- CGP detector -- X-Listserver = CommuniGate Pro LIST -- RFC 2919 headers exist -- function check_ml_cgp(task) local message = task:get_message() local header = message:get_header('X-Listserver') if not header or header ~= 'CommuniGate Pro LIST' then return false end return check_rfc2919(task) end 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