local rcvd_cb_id = rspamd_config:register_symbol{
name = 'CHECK_RECEIVED',
type = 'callback',
+ score = 0.0,
+ group = 'headers',
callback = function(task)
local cnts = {
[1] = 'ONE',
local prio_cb_id = rspamd_config:register_symbol {
name = 'HAS_X_PRIO',
type = 'callback',
+ score = 0.0,
+ group = 'headers',
callback = function (task)
local cnts = {
[1] = 'ONE',
return ((task:get_header_full(name) or {})[1] or {})['value']
end
-local check_replyto_id = rspamd_config:register_callback_symbol('CHECK_REPLYTO', 1.0,
- function (task)
+local check_replyto_id = rspamd_config:register_symbol({
+ type = 'callback',
+ name = 'CHECK_REPLYTO',
+ score = 0.0,
+ group = 'headers',
+ callback = function(task)
local replyto = get_raw_header(task, 'Reply-To')
- if not replyto then return false end
+ if not replyto then
+ return false
+ end
local rt = util.parse_mail_address(replyto, task:get_mempool())
if not (rt and rt[1] and (string.len(rt[1].addr) > 0)) then
task:insert_result('REPLYTO_UNPARSEABLE', 1.0)
-- See if Reply-To matches From in some way
local from = task:get_from(2)
local from_h = get_raw_header(task, 'From')
- if not (from and from[1]) then return false end
+ if not (from and from[1]) then
+ return false
+ end
if (from_h and from_h == replyto) then
-- From and Reply-To are identical
task:insert_result('REPLYTO_EQ_FROM', 1.0)
-- See if Reply-To matches the To address
local to = task:get_recipients(2)
if (to and to[1] and to[1].addr:lower() == rt[1].addr:lower()) then
- -- Ignore this for mailing-lists and automatic submissions
- if (not (task:get_header('List-Unsubscribe') or
- task:get_header('X-To-Get-Off-This-List') or
- task:get_header('X-List') or
- task:get_header('Auto-Submitted')))
- then
- task:insert_result('REPLYTO_EQ_TO_ADDR', 1.0)
- end
+ -- Ignore this for mailing-lists and automatic submissions
+ if (not (task:get_header('List-Unsubscribe') or
+ task:get_header('X-To-Get-Off-This-List') or
+ task:get_header('X-List') or
+ task:get_header('Auto-Submitted')))
+ then
+ task:insert_result('REPLYTO_EQ_TO_ADDR', 1.0)
+ end
else
task:insert_result('REPLYTO_DOM_NEQ_FROM_DOM', 1.0)
- end
+ end
end
end
-- See if the Display Names match
end
end
end
-)
+})
rspamd_config:register_symbol{
name = 'REPLYTO_UNPARSEABLE',
local check_mime_id = rspamd_config:register_symbol{
name = 'CHECK_MIME',
type = 'callback',
+ group = 'headers',
+ score = 0.0,
callback = function(task)
local parts = task:get_parts()
if not parts then return false end
end
end,
description = 'Message either to a list or was forwarded',
+ group = 'headers',
score = 0.0
}
rspamd_config.BROKEN_HEADERS = {
local check_from_id = rspamd_config:register_symbol{
name = 'CHECK_FROM',
type = 'callback',
+ score = 0.0,
+ group = 'headers',
callback = function(task)
local envfrom = task:get_from(1)
local from = task:get_from(2)
local check_to_cc_id = rspamd_config:register_symbol{
name = 'CHECK_TO_CC',
type = 'callback',
+ score = 0.0,
+ group = 'headers',
callback = function(task)
local rcpts = task:get_recipients(1)
local to = task:get_recipients(2)
re = string.format('!(%s) & !(%s) & (%s)', subject_encoded_b64, subject_encoded_qp, subject_needs_mime),
score = 1.0,
description = 'Subject needs encoding',
- group = 'header'
+ group = 'headers'
}
local from_encoded_b64 = 'From=/=\\?\\S+\\?B\\?/iX'
re = string.format('!(%s) & !(%s) & (%s)', from_encoded_b64, from_encoded_qp, raw_from_needs_mime),
score = 1.0,
description = 'From header needs encoding',
- group = 'header'
+ group = 'headers'
}
local to_encoded_b64 = 'To=/=\\?\\S+\\?B\\?/iX'
re = string.format('!(%s) & !(%s) & (%s)', to_encoded_b64, to_encoded_qp, raw_to_needs_mime),
score = 1.0,
description = 'To header needs encoding',
- group = 'header'
+ group = 'headers'
}
-- Detects that there is no space in From header (e.g. Some Name<some@host>)
re = 'From=/\\S<[-\\w\\.]+\\@[-\\w\\.]+>/X',
score = 1.0,
description = 'No space in from header',
- group = 'header'
+ group = 'headers'
}
reconf['TO_WRAPPED_IN_SPACES'] = {
re = [[To=/<\s[-.\w]+\@[-.\w]+\s>/X]],
score = 2.0,
description = 'To address is wrapped in spaces inside angle brackets (e.g. display-name < local-part@domain >)',
- group = 'header'
+ group = 'headers'
}
-- Detects missing Subject header
re = '!raw_header_exists(Subject)',
score = 2.0,
description = 'Subject header is missing',
- group = 'header'
+ group = 'headers'
}
rspamd_config.EMPTY_SUBJECT = {
score = 1.0,
description = 'Subject header is empty',
- group = 'header',
+ group = 'headers',
callback = function(task)
local hdr = task:get_header('Subject')
if hdr and #hdr == 0 then
re = '!raw_header_exists(To)',
score = 2.0,
description = 'To header is missing',
- group = 'header'
+ group = 'headers'
}
-- Detects undisclosed recipients
re = string.format('(%s)', undisc_rcpt),
score = 3.0,
description = 'Recipients are absent or undisclosed',
- group = 'header'
+ group = 'headers'
}
-- Detects missing Message-Id
re = '!header_exists(Message-Id)',
score = 2.5,
description = 'Message id is missing',
- group = 'header'
+ group = 'headers'
}
-- Received seems to be fake
re = 'Received=/^from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by [-.\\w+]{5,255}; [SMTWF][a-z][a-z], [\\s\\d]?\\d [JFMAJSOND][a-z][a-z] \\d{4} \\d{2}:\\d{2}:\\d{2} [-+]\\d{4}$/mH',
score = 3.0,
description = 'Spambots signatures in received headers',
- group = 'header'
+ group = 'headers'
}
-- Charset is missing in message
'compare_transfer_encoding(7bit)'),
score = 2.5,
description = 'Charset is missing in a message',
- group = 'header'
+ group = 'headers'
}
-- Subject seems to be spam
re = 'Subject=/\\bsajding(?:om|a)?\\b/iH',
score = 8.0,
description = 'Subject seems to be spam',
- group = 'header'
+ group = 'headers'
}
-- Find forged Outlook MUA
re = string.format('!%s & %s & %s', yahoo_bulk, outlook_mua, 'has_only_html_part()'),
score = 5.0,
description = 'Forged outlook HTML signature',
- group = 'header'
+ group = 'headers'
}
-- Recipients seems to be likely with each other (only works when recipients count is more than 5 recipients)
re = 'compare_recipients_distance(0.65)',
score = 1.5,
description = 'Recipients seems to be autogenerated (works if recipients count is more than 5)',
- group = 'header'
+ group = 'headers'
}
-- Recipients list seems to be sorted
re = 'is_recipients_sorted()',
score = 3.5,
description = 'Recipients list seems to be sorted',
- group = 'header'
+ group = 'headers'
}
-- Spam string at the end of message to make statistics faults
re = '/^[a-z0-9]{6,24}[-_a-z0-9]{12,36}[a-z0-9]{6,24}\\s*\\z/isPr',
score = 3.84,
description = 'Spam string at the end of message to make statistics fault',
- group = 'header'
+ group = 'headers'
}
-- From contains only 7bit characters (parsed headers are used)
tag_exists_meta, tag_exists_body),
score = 2.1,
description = "Message pretends to be send from Outlook but has 'strange' tags",
- group = 'header'
+ group = 'headers'
}
-- Forged OE/MSO boundary
re = string.format('(%s) & !((%s) | (%s))', has_mid, sane_msgid, msgid_comment),
score = 1.7,
description = 'Message id is incorrect',
- group = 'header'
+ group = 'headers'
}
re = string.format('!(%s) & !(%s) & (%s) & !(%s) & !(%s)', cd, cte, ct, mime_version, ct_text_plain),
score = 2.0,
description = 'Only Content-Type header without other MIME headers',
- group = 'header'
+ group = 'headers'
}
re = string.format('(%s) & !(%s) & !(%s)', msgid_dollars_ok, mimeole_ms, rcvd_with_exchange),
score = 2.0,
description = 'Forged Exchange messages',
- group = 'header'
+ group = 'headers'
}
-- Reply-type in content-type
re = 'Content-Type=/text\\/plain; .* reply-type=original/H',
score = 1.0,
description = 'Reply-type in content-type',
- group = 'header'
+ group = 'headers'
}
-- Fake Verizon headers
re = string.format('(%s) & !(%s)', fhelo_verizon, fhost_verizon),
score = 2.0,
description = 'Fake helo for verizon provider',
- group = 'header'
+ group = 'headers'
}
-- Forged yahoo msgid
re = string.format('(%s) & !(%s)', at_yahoo_msgid, from_yahoo_com),
score = 2.0,
description = 'Forged yahoo msgid',
- group = 'header'
+ group = 'headers'
}
-- Forged The Bat! MUA headers
re = string.format('(%s) & (%s) & !(%s) & !(%s)', thebat_mua_v1, ctype_has_boundary, bat_boundary, mailman_21),
score = 2.0,
description = 'Forged The Bat! MUA headers',
- group = 'header'
+ group = 'headers'
}
-- Detect Mail.Ru web-mail
re = string.format('(%s) & (%s)', xm_mail_ru_mailer_1_0, rcvd_e_mail_ru),
score = 0.0,
description = 'Sent with Mail.Ru web-mail',
- group = 'header'
+ group = 'headers'
}
-- Detect yandex.ru web-mail
re = string.format('(%s) & (%s)', xm_yandex_ru_mailer_5_0, rcvd_web_yandex_ru),
score = 0.0,
description = 'Sent with yandex.ru web-mail',
- group = 'header'
+ group = 'headers'
}
-- Detect 1C v8.2 and v8.3 mailers
re = 'X-Mailer=/^1C:Enterprise 8\\.[23]$/H',
score = 0.0,
description = 'Sent with 1C:Enterprise 8',
- group = 'header'
+ group = 'headers'
}
-- Detect rogue 'strongmail' MTA with IPv4 and '(-)' in Received line
re = [[Received=/^from\s+strongmail\s+\(\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\]\) by \S+ \(-\); /mH]],
score = 6.0,
description = 'Sent via rogue "strongmail" MTA',
- group = 'header'
+ group = 'headers'
}
-- Two received headers with ip addresses
re = string.format('(%s) | (%s)', double_ip_spam_1, double_ip_spam_2),
score = 2.0,
description = 'Two received headers with ip addresses',
- group = 'header'
+ group = 'headers'
}
-- Quoted reply-to from yahoo (seems to be forged)
re = string.format('(%s) & ((%s) | (%s))', repto_quote, from_yahoo_com, at_yahoo_msgid),
score = 2.0,
description = 'Quoted reply-to from yahoo (seems to be forged)',
- group = 'header'
+ group = 'headers'
}
-- MUA definitions
has_office_version_in_mailer),
score = 2.0,
description = 'Mime-OLE is needed but absent (e.g. fake Outlook or fake Exchange)',
- group = 'header'
+ group = 'headers'
}
-- Header delimiters
re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(From)', yandex),
score = 1.0,
description = 'Header From begins with tab',
- group = 'header'
+ group = 'headers'
}
reconf['HEADER_TO_DELIMITER_TAB'] = {
re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(To)', yandex),
score = 1.0,
description = 'Header To begins with tab',
- group = 'header'
+ group = 'headers'
}
reconf['HEADER_CC_DELIMITER_TAB'] = {
re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Cc)', yandex),
score = 1.0,
description = 'Header To begins with tab',
- group = 'header'
+ group = 'headers'
}
reconf['HEADER_REPLYTO_DELIMITER_TAB'] = {
re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Reply-To)', yandex),
score = 1.0,
description = 'Header Reply-To begins with tab',
- group = 'header'
+ group = 'headers'
}
reconf['HEADER_DATE_DELIMITER_TAB'] = {
re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Date)', yandex),
score = 1.0,
description = 'Header Date begins with tab',
- group = 'header'
+ group = 'headers'
}
-- Empty delimiters between header names and header values
function check_header_delimiter_empty(task, header_name)
re = string.format('(%s)', 'check_header_delimiter_empty(From)'),
score = 1.0,
description = 'Header From has no delimiter between header name and header value',
- group = 'header'
+ group = 'headers'
}
reconf['HEADER_TO_EMPTY_DELIMITER'] = {
re = string.format('(%s)', 'check_header_delimiter_empty(To)'),
score = 1.0,
description = 'Header To has no delimiter between header name and header value',
- group = 'header'
+ group = 'headers'
}
reconf['HEADER_CC_EMPTY_DELIMITER'] = {
re = string.format('(%s)', 'check_header_delimiter_empty(Cc)'),
score = 1.0,
description = 'Header Cc has no delimiter between header name and header value',
- group = 'header'
+ group = 'headers'
}
reconf['HEADER_REPLYTO_EMPTY_DELIMITER'] = {
re = string.format('(%s)', 'check_header_delimiter_empty(Reply-To)'),
score = 1.0,
description = 'Header Reply-To has no delimiter between header name and header value',
- group = 'header'
+ group = 'headers'
}
reconf['HEADER_DATE_EMPTY_DELIMITER'] = {
re = string.format('(%s)', 'check_header_delimiter_empty(Date)'),
score = 1.0,
description = 'Header Date has no delimiter between header name and header value',
- group = 'header'
+ group = 'headers'
}
-- Definitions of received headers regexp
re = 'Received=/[\\x80-\\xff]/X',
score = 4.0,
description = 'Header Received has raw illegal character',
- group = 'header'
+ group = 'headers'
}
local MAIL_RU_Return_Path = 'Return-path=/^\\s*<.+\\@mail\\.ru>$/iX'
re = string.format('(%s) & !(((%s) | (%s)) & (%s))', MAIL_RU_Received, MAIL_RU_Return_Path, MAIL_RU_X_Envelope_From, MAIL_RU_From),
score = 4.0,
description = 'Fake helo mail.ru in header Received from non mail.ru sender address',
- group = 'header'
+ group = 'headers'
}
local GMAIL_COM_Return_Path = 'Return-path=/^\\s*<.+\\@gmail\\.com>$/iX'
re = string.format('(((%s) & ((%s) | (%s))) | ((%s) & ((%s) | (%s))) | ((%s) & ((%s) | (%s)))) & (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s) | (%s)', MAIL_RU_From, MAIL_RU_Return_Path, MAIL_RU_X_Envelope_From, GMAIL_COM_From, GMAIL_COM_Return_Path, GMAIL_COM_X_Envelope_From, UKR_NET_From, UKR_NET_Return_Path, UKR_NET_X_Envelope_From, RECEIVED_smtp_yandex_ru_1, RECEIVED_smtp_yandex_ru_2, RECEIVED_smtp_yandex_ru_3, RECEIVED_smtp_yandex_ru_4, RECEIVED_smtp_yandex_ru_5, RECEIVED_smtp_yandex_ru_6, RECEIVED_smtp_yandex_ru_7, RECEIVED_smtp_yandex_ru_8, RECEIVED_smtp_yandex_ru_9),
score = 4.0,
description = 'Fake smtp.yandex.ru Received',
- group = 'header'
+ group = 'headers'
}
reconf['FORGED_GENERIC_RECEIVED'] = {
re = 'Received=/^\\s*(.+\\n)*from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by (([\\w\\d-]+\\.)+[a-zA-Z]{2,6}|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}); \\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0/X',
score = 3.6,
description = 'Forged generic Received',
- group = 'header'
+ group = 'headers'
}
reconf['FORGED_GENERIC_RECEIVED2'] = {
re = 'Received=/^\\s*(.+\\n)*from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by ([\\w\\d-]+\\.)+[a-z]{2,6} id [\\w\\d]{12}; \\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0/X',
score = 3.6,
description = 'Forged generic Received',
- group = 'header'
+ group = 'headers'
}
reconf['FORGED_GENERIC_RECEIVED3'] = {
re = 'Received=/^\\s*(.+\\n)*by \\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} with SMTP id [a-zA-Z]{14}\\.\\d{13};[\\r\\n\\s]*\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0 \\(GMT\\)/X',
score = 3.6,
description = 'Forged generic Received',
- group = 'header'
+ group = 'headers'
}
reconf['FORGED_GENERIC_RECEIVED4'] = {
re = 'Received=/^\\s*(.+\\n)*from localhost by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0[\\s\\r\\n]*$/X',
score = 3.6,
description = 'Forged generic Received',
- group = 'header'
+ group = 'headers'
}
reconf['INVALID_POSTFIX_RECEIVED'] = {
re = 'Received=/ \\(Postfix\\) with ESMTP id [A-Z\\d]+([\\s\\r\\n]+for <\\S+?>)?;[\\s\\r\\n]*[A-Z][a-z]{2}, \\d{1,2} [A-Z][a-z]{2} \\d\\d\\d\\d \\d\\d:\\d\\d:\\d\\d [\\+\\-]\\d\\d\\d\\d$/X',
score = 3.0,
description = 'Invalid Postfix Received',
- group = 'header'
+ group = 'headers'
}
reconf['X_PHP_FORGED_0X'] = {
re = "X-PHP-Originating-Script=/^0\\d/X",
score = 4.0,
description = "X-PHP-Originating-Script header appears forged",
- group = 'header'
+ group = 'headers'
}
reconf['GOOGLE_FORWARDING_MID_MISSING'] = {
re = "Message-ID=/SMTPIN_ADDED_MISSING\\@mx\\.google\\.com>$/X",
score = 2.5,
description = "Message was missing Message-ID pre-forwarding",
- group = 'header'
+ group = 'headers'
}
reconf['GOOGLE_FORWARDING_MID_BROKEN'] = {
re = "Message-ID=/SMTPIN_ADDED_BROKEN\\@mx\\.google\\.com>$/X",
score = 1.7,
description = "Message had invalid Message-ID pre-forwarding",
- group = 'header'
+ group = 'headers'
}
reconf['CTE_CASE'] = {
re = 'Content-Transfer-Encoding=/^[78]B/X',
description = '[78]Bit .vs. [78]bit',
score = 0.5,
- group = 'header'
+ group = 'headers'
}
reconf['HAS_INTERSPIRE_SIG'] = {
'List-Unsubscribe=/\\/unsubscribe\\.php\\?M=[^&]+&C=[^&]+&L=[^&]+&N=[^>]+>$/Xi'),
description = "Has Interspire fingerprint",
score = 1.0,
- group = 'header'
+ group = 'headers'
}
reconf['CT_EXTRA_SEMI'] = {
re = 'Content-Type=/;$/X',
description = 'Content-Type ends with a semi-colon',
score = 1.0,
- group = 'header'
+ group = 'headers'
}
reconf['SUBJECT_ENDS_EXCLAIM'] = {