]> source.dussan.org Git - rspamd.git/commitdiff
[Minor] Update rule style: headers.lua 910/head
authorAndrew Lewis <nerf@judo.za.org>
Tue, 30 Aug 2016 13:45:10 +0000 (15:45 +0200)
committerAndrew Lewis <nerf@judo.za.org>
Tue, 30 Aug 2016 13:45:10 +0000 (15:45 +0200)
conf/metrics.conf
rules/regexp/headers.lua

index 0bab8ea51aa9b49a9c3789efcfc2dc197afad630..1294ca2f1362e9bf6b2a9fa5c3cbe7ffea8d4a64 100644 (file)
@@ -28,99 +28,23 @@ metric {
 
     group "excessqp" {
         max_score = 2.4;
-        symbol "FROM_EXCESS_QP" {
-            weight = 1.2;
-            description = "From that contains encoded characters while quoted-printable is not needed as all symbols are 7bit";
-        }
-        symbol "TO_EXCESS_QP" {
-            weight = 1.2;
-            description = "To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit";
-        }
-        symbol "REPLYTO_EXCESS_QP" {
-            weight = 1.2;
-            description = "Reply-To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit";
-        }
-        symbol "CC_EXCESS_QP" {
-            weight = 1.2;
-            description = "Cc that contains encoded characters while quoted-printable is not needed as all symbols are 7bit";
-        }
     }
     group "excessb64" {
         max_score = 3.0;
-        symbol "FROM_EXCESS_BASE64" {
-            weight = 1.5;
-            description = "From that contains encoded characters while base 64 is not needed as all symbols are 7bit";
-        }
-        symbol "TO_EXCESS_BASE64" {
-            weight = 1.5;
-            description = "To that contains encoded characters while base 64 is not needed as all symbols are 7bit";
-        }
-        symbol "REPLYTO_EXCESS_BASE64" {
-            weight = 1.5;
-            description = "Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit";
-        }
-        symbol "CC_EXCESS_BASE64" {
-            weight = 1.5;
-            description = "Cc that contains encoded characters while base 64 is not needed as all symbols are 7bit";
-        }
     }
     group "header" {
-        symbol "MISSING_SUBJECT" {
-            weight = 2.0;
-            description = "Subject is missing inside message";
-        }
-        symbol "FORGED_OUTLOOK_TAGS" {
-            weight = 2.100000;
-            description = "Message pretends to be send from Outlook but has 'strange' tags ";
-        }
         symbol "FORGED_SENDER" {
             weight = 0.30;
             description = "Sender is forged (different From: header and smtp MAIL FROM: addresses)";
         }
-        symbol "SUSPICIOUS_RECIPS" {
-            weight = 1.500000;
-            description = "Recipients seems to be autogenerated (works if recipients count is more than 5)";
-        }
         symbol "MIME_HTML_ONLY" {
             weight = 0.2;
             description = "Messages that have only HTML part";
         }
-        symbol "FORGED_MSGID_YAHOO" {
-            weight = 2.0;
-            description = "Forged yahoo msgid";
-        }
-        symbol "FORGED_MUA_THEBAT_BOUN" {
-            weight = 2.0;
-            description = "Forged The Bat! MUA headers";
-        }
-        symbol "R_MISSING_CHARSET" {
-            weight = 2.5;
-            description = "Charset is missing in a message";
-        }
-        symbol "RCVD_DOUBLE_IP_SPAM" {
-            weight = 2.0;
-            description = "Two received headers with ip addresses";
-        }
-        symbol "FORGED_OUTLOOK_HTML" {
-            weight = 5.0;
-            description = "Forged outlook HTML signature";
-        }
-        symbol "R_UNDISC_RCPT" {
-            weight = 3.0;
-            description = "Recipients are absent or undisclosed";
-        }
         symbol "FM_FAKE_HELO_VERIZON" {
             weight = 2.0;
             description = "Fake helo for verizon provider";
         }
-        symbol "REPTO_QUOTE_YAHOO" {
-            weight = 2.0;
-            description = "Quoted reply-to from yahoo (seems to be forged)";
-        }
-        symbol "MISSING_MIMEOLE" {
-            weight = 2.0;
-            description = "Mime-OLE is needed but absent (e.g. fake Outlook or fake Exchange)";
-        }
         symbol "MISSING_TO" {
             weight = 2.0;
             description = "To header is missing";
@@ -135,42 +59,6 @@ metric {
             description = "Mixed characters in a URL inside message";
             one_shot = true;
         }
-        symbol "SORTED_RECIPS" {
-            weight = 3.500000;
-            description = "Recipients list seems to be sorted";
-        }
-        symbol "R_RCVD_SPAMBOTS" {
-            weight = 3.0;
-            description = "Spambots signatures in received headers";
-        }
-        symbol "SUBJECT_NEEDS_ENCODING" {
-            weight = 1.0;
-            description = "Subject needs encoding";
-        }
-        symbol "TRACKER_ID" {
-            weight = 3.84;
-            description = "Spam string at the end of message to make statistics faults 0";
-        }
-        symbol "R_NO_SPACE_IN_FROM" {
-            weight = 1.0;
-            description = "No space in from header";
-        }
-        symbol "R_SAJDING" {
-            weight = 8.0;
-            description = "Subject seems to be spam";
-        }
-        symbol "R_BAD_CTE_7BIT" {
-            weight = 3.0;
-            description = "Detects bad content-transfer-encoding for text parts";
-        }
-        symbol "INVALID_MSGID" {
-            weight = 1.7;
-            description = "Message id is incorrect";
-        }
-        symbol "MISSING_MID" {
-            weight = 2.5;
-            description = "Message id is missing ";
-        }
         symbol "FORGED_RECIPIENTS" {
             weight = 2.0;
             description = "Recipients are not the same as RCPT TO: mail command";
@@ -183,14 +71,6 @@ metric {
             weight = 0.0;
             description = "Sender is not the same as MAIL FROM: envelope, but a message is from a maillist";
         }
-        symbol "RATWARE_MS_HASH" {
-            weight = 2.0;
-            description = "Forged Exchange messages";
-        }
-        symbol "STOX_REPLY_TYPE" {
-            weight = 1.0;
-            description = "Reply-type in content-type";
-        }
         symbol "ONCE_RECEIVED" {
             weight = 0.1;
             description = "One received header in a message";
@@ -203,99 +83,15 @@ metric {
             weight = 4.0;
             description = "One received header with 'bad' patterns inside";
         }
-        symbol "MIME_HEADER_CTYPE_ONLY" {
-            weight = 2.0;
-            description = "Only Content-Type header without other MIME headers";
-        }
         symbol "MAILLIST" {
             weight = -0.2;
             description = "Message seems to be from maillist";
         }
-        symbol "HEADER_FROM_DELIMITER_TAB" {
-            weight = 1.0;
-            description = "Header From begins with tab";
-        }
-        symbol "HEADER_TO_DELIMITER_TAB" {
-            weight = 1.0;
-            description = "Header To begins with tab";
-        }
-        symbol "HEADER_CC_DELIMITER_TAB" {
-            weight = 1.0;
-            description = "Header Cc begins with tab";
-        }
-        symbol "HEADER_REPLYTO_DELIMITER_TAB" {
-            weight = 1.0;
-            description = "Header Reply-To begins with tab";
-        }
-        symbol "HEADER_DATE_DELIMITER_TAB" {
-            weight = 1.0;
-            description = "Header Date begins with tab";
-        }
-        symbol "HEADER_FROM_EMPTY_DELIMITER" {
-            weight = 1.0;
-            description = "Header From has no delimiter between header name and header value";
-        }
-        symbol "HEADER_TO_EMPTY_DELIMITER" {
-            weight = 1.0;
-            description = "Header To has no delimiter between header name and header value";
-        }
-        symbol "HEADER_CC_EMPTY_DELIMITER" {
-            weight = 1.0;
-            description = "Header Cc has no delimiter between header name and header value";
-        }
-        symbol "HEADER_REPLYTO_EMPTY_DELIMITER" {
-            weight = 1.0;
-            description = "Header Reply-To has no delimiter between header name and header value";
-        }
-        symbol "HEADER_DATE_EMPTY_DELIMITER" {
-            weight = 1.0;
-            description = "Header Date has no delimiter between header name and header value";
-        }
-        symbol "RCVD_ILLEGAL_CHARS" {
-            weight = 4.0;
-            description = "Header Received has raw illegal character";
-        }
-        symbol "FAKE_RECEIVED_mail_ru" {
-            weight = 4.0;
-            description = "Fake helo mail.ru in header Received from non mail.ru sender address";
-        }
-        symbol "FAKE_RECEIVED_smtp_yandex_ru" {
-            weight = 4.0;
-            description = "Fake smtp.yandex.ru Received";
-        }
-        symbol "FORGED_GENERIC_RECEIVED" {
-            weight = 3.6;
-            description = "Forged generic Received";
-        }
-        symbol "FORGED_GENERIC_RECEIVED2" {
-            weight = 3.6;
-            description = "Forged generic Received";
-        }
-        symbol "FORGED_GENERIC_RECEIVED3" {
-            weight = 3.6;
-            description = "Forged generic Received";
-        }
-        symbol "FORGED_GENERIC_RECEIVED4" {
-            weight = 3.6;
-            description = "Forged generic Received";
-        }
-        symbol "FORGED_GENERIC_RECEIVED5" {
-            weight = 4.6;
-            description = "Forged generic Received";
-        }
-        symbol "INVALID_POSTFIX_RECEIVED" {
-            weight = 3.0;
-            description = "Invalid Postfix Received";
-        }
     }
 
     group "subject" {
         max_score = 6.0;
 
-        symbol "FAKE_REPLY_C" {
-            weight = 6.0;
-            description = "Fake reply (has RE in subject, but has not References header)";
-        }
         symbol "LONG_SUBJ" {
             weight = 6.0;
             description = "Subject is too long";
@@ -307,58 +103,6 @@ metric {
     }
 
     group "mua" {
-        symbol "FORGED_MUA_THEBAT_MSGID" {
-            weight = 4.0;
-            description = "Message pretends to be send from The Bat! but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_THEBAT_MSGID_UNKNOWN" {
-            weight = 3.0;
-            description = "Message pretends to be send from The Bat! but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_KMAIL_MSGID" {
-            weight = 3.0;
-            description = "Message pretends to be send from KMail but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_KMAIL_MSGID_UNKNOWN" {
-            weight = 2.5;
-            description = "Message pretends to be send from KMail but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_OPERA_MSGID" {
-            weight = 4.0;
-            description = "Message pretends to be send from Opera Mail but has forged Message-ID";
-        }
-        symbol "SUSPICIOUS_OPERA_10W_MSGID" {
-            weight = 4.0;
-            description = "Message pretends to be send from suspicious Opera Mail/10.x (Windows) but has forged Message-ID, apparently from KMail";
-        }
-        symbol "FORGED_MUA_MOZILLA_MAIL_MSGID" {
-            weight = 4.0;
-            description = "Message pretends to be send from Mozilla Mail but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN" {
-            weight = 2.5;
-            description = "Message pretends to be send from Mozilla Mail but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_THUNDERBIRD_MSGID" {
-            weight = 4.0;
-            description = "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN" {
-            weight = 2.5;
-            description = "Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_SEAMONKEY_MSGID" {
-            weight = 4.0;
-            description = "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN" {
-            weight = 2.5;
-            description = "Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID";
-        }
-        symbol "FORGED_MUA_OUTLOOK" {
-            weight = 3.0;
-            description = "Forged outlook MUA";
-        }
         symbol "FORGED_MUA_MAILLIST" {
             weight = 0.0;
             description = "Avoid false positives for FORGED_MUA_* in maillist";
@@ -382,22 +126,6 @@ metric {
             weight = 0.5;
             description = "Short html part with a link to an image";
         }
-        symbol "SUSPICIOUS_BOUNDARY" {
-            weight = 5.0;
-            description = "Suspicious boundary in header Content-Type";
-        }
-        symbol "SUSPICIOUS_BOUNDARY2" {
-            weight = 4.0;
-            description = "Suspicious boundary in header Content-Type";
-        }
-        symbol "SUSPICIOUS_BOUNDARY3" {
-            weight = 3.0;
-            description = "Suspicious boundary in header Content-Type";
-        }
-        symbol "SUSPICIOUS_BOUNDARY4" {
-            weight = 4.0;
-            description = "Suspicious boundary in header Content-Type";
-        }
         symbol "R_PARTS_DIFFER" {
             weight = 1.0;
             description = "Text and HTML parts differ";
index 6ec37181fa473021b0ef697abf6bdef7440fb89d..e645ff007c843d1258ac51a905835596004c3087 100644 (file)
@@ -27,21 +27,35 @@ local subject_encoded_qp = 'Subject=/=\\?\\S+\\?Q\\?/iX'
 -- Define whether subject must be encoded (contains non-7bit characters)
 local subject_needs_mime = 'Subject=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/X'
 -- Final rule
-reconf['SUBJECT_NEEDS_ENCODING'] = string.format('!(%s) & !(%s) & (%s)', subject_encoded_b64, subject_encoded_qp, subject_needs_mime)
+reconf['SUBJECT_NEEDS_ENCODING'] = {
+  re = string.format('!(%s) & !(%s) & (%s)', subject_encoded_b64, subject_encoded_qp, subject_needs_mime),
+  score = 1.0,
+  description = 'Subject needs encoding',
+  group = 'header'
+}
 
 -- Detects that there is no space in From header (e.g. Some Name<some@host>)
-reconf['R_NO_SPACE_IN_FROM'] = 'From=/\\S<[-\\w\\.]+\\@[-\\w\\.]+>/X'
+reconf['R_NO_SPACE_IN_FROM'] = {
+  re = 'From=/\\S<[-\\w\\.]+\\@[-\\w\\.]+>/X',
+  score = 1.0,
+  description = 'No space in from header',
+  group = 'header'
+}
 
+rspamd_config.MISSING_SUBJECT = {
+  score = 2.0,
+  description = 'Subject is missing inside message',
+  group = 'header',
+  callback = function(task)
+    local hdr = task:get_header('Subject')
 
-rspamd_config.MISSING_SUBJECT = function(task)
-  local hdr = task:get_header('Subject')
+    if not hdr or #hdr == 0 then
+      return true
+    end
 
-  if not hdr or #hdr == 0 then
-    return true
+    return false
   end
-
-  return false
-end
+}
 
 -- Detects bad content-transfer-encoding for text parts
 -- For text parts (text/plain and text/html mainly)
@@ -50,27 +64,62 @@ local r_ctype_text = 'content_type_is_type(text)'
 local r_cte_7bit = 'compare_transfer_encoding(7bit)'
 -- And body contains 8bit characters
 local r_body_8bit = '/[^\\x01-\\x7f]/Pr'
-reconf['R_BAD_CTE_7BIT'] = string.format('(%s) & (%s) & (%s)', r_ctype_text, r_cte_7bit, r_body_8bit)
+reconf['R_BAD_CTE_7BIT'] = {
+  re = string.format('(%s) & (%s) & (%s)', r_ctype_text, r_cte_7bit, r_body_8bit),
+  score = 3.0,
+  description = 'Detects bad content-transfer-encoding for text parts',
+  group = 'header'
+}
 
 -- Detects missing To header
-reconf['MISSING_TO']= '!raw_header_exists(To)';
+reconf['MISSING_TO'] = {
+  re = '!raw_header_exists(To)',
+  score = 2.0,
+  description = 'To header is missing',
+  group = 'header'
+}
 
 -- Detects undisclosed recipients
 local undisc_rcpt = 'To=/^<?undisclosed[- ]recipient/Hi'
-reconf['R_UNDISC_RCPT'] = string.format('(%s)', undisc_rcpt)
+reconf['R_UNDISC_RCPT'] = {
+  re = string.format('(%s)', undisc_rcpt),
+  score = 3.0,
+  description = 'Recipients are absent or undisclosed',
+  group = 'header'
+}
 
 -- Detects missing Message-Id
 local has_mid = 'header_exists(Message-Id)'
-reconf['MISSING_MID'] = '!header_exists(Message-Id)';
+reconf['MISSING_MID'] = {
+  re = '!header_exists(Message-Id)',
+  score = 2.5,
+  description = 'Message id is missing',
+  group = 'header'
+}
 
 -- Received seems to be fake
-reconf['R_RCVD_SPAMBOTS'] = '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'
+reconf['R_RCVD_SPAMBOTS'] = {
+  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'
+}
 
 -- Charset is missing in message
-reconf['R_MISSING_CHARSET']= string.format('content_type_is_type(text) & !content_type_has_param(charset) & !%s', r_cte_7bit);
+reconf['R_MISSING_CHARSET'] = {
+  re = string.format('content_type_is_type(text) & !content_type_has_param(charset) & !%s', r_cte_7bit),
+  score = 2.5,
+  description = 'Charset is missing in a message',
+  group = 'header'
+}
 
 -- Subject seems to be spam
-reconf['R_SAJDING'] = 'Subject=/\\bsajding(?:om|a)?\\b/iH'
+reconf['R_SAJDING'] = {
+  re = 'Subject=/\\bsajding(?:om|a)?\\b/iH',
+  score = 8.0,
+  description = 'Subject seems to be spam',
+  group = 'header'
+}
 
 -- Find forged Outlook MUA
 -- Yahoo groups messages
@@ -78,16 +127,36 @@ local yahoo_bulk = 'Received=/from \\[\\S+\\] by \\S+\\.(?:groups|scd|dcn)\\.yah
 -- Outlook MUA
 local outlook_mua = 'X-Mailer=/^Microsoft Outlook\\b/H'
 local any_outlook_mua = 'X-Mailer=/^Microsoft Outlook\\b/H'
-reconf['FORGED_OUTLOOK_HTML'] = string.format('!%s & %s & %s', yahoo_bulk, outlook_mua, 'has_only_html_part()')
+reconf['FORGED_OUTLOOK_HTML'] = {
+  re = string.format('!%s & %s & %s', yahoo_bulk, outlook_mua, 'has_only_html_part()'),
+  score = 5.0,
+  description = 'Forged outlook HTML signature',
+  group = 'header'
+}
 
 -- Recipients seems to be likely with each other (only works when recipients count is more than 5 recipients)
-reconf['SUSPICIOUS_RECIPS'] = 'compare_recipients_distance(0.65)'
+reconf['SUSPICIOUS_RECIPS'] = {
+  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'
+}
 
 -- Recipients list seems to be sorted
-reconf['SORTED_RECIPS'] = 'is_recipients_sorted()'
+reconf['SORTED_RECIPS'] = {
+  re = 'is_recipients_sorted()',
+  score = 3.5,
+  description = 'Recipients list seems to be sorted',
+  group = 'header'
+}
 
 -- Spam string at the end of message to make statistics faults
-reconf['TRACKER_ID'] = '/^[a-z0-9]{6,24}[-_a-z0-9]{12,36}[a-z0-9]{6,24}\\s*\\z/isPr'
+reconf['TRACKER_ID'] = {
+  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'
+}
 
 
 -- From that contains encoded characters while base 64 is not needed as all symbols are 7bit
@@ -96,13 +165,23 @@ local from_encoded_b64 = 'From=/\\=\\?\\S+\\?B\\?/iX'
 -- From contains only 7bit characters (parsed headers are used)
 local from_needs_mime = 'From=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/Hr'
 -- Final rule
-reconf['FROM_EXCESS_BASE64'] = string.format('%s & !%s', from_encoded_b64, from_needs_mime)
+reconf['FROM_EXCESS_BASE64'] = {
+  re = string.format('%s & !%s', from_encoded_b64, from_needs_mime),
+  score = 1.5,
+  description = 'From that contains encoded characters while base 64 is not needed as all symbols are 7bit',
+  group = 'excessb64'
+}
 
 -- From that contains encoded characters while quoted-printable is not needed as all symbols are 7bit
 -- Regexp that checks that From header is encoded with quoted-printable (search in raw headers)
 local from_encoded_qp = 'From=/\\=\\?\\S+\\?Q\\?/iX'
 -- Final rule
-reconf['FROM_EXCESS_QP'] = string.format('%s & !%s', from_encoded_qp, from_needs_mime)
+reconf['FROM_EXCESS_QP'] = {
+  re = string.format('%s & !%s', from_encoded_qp, from_needs_mime),
+  score = 1.2,
+  description = 'From that contains encoded characters while quoted-printable is not needed as all symbols are 7bit',
+  group = 'excessqp'
+}
 
 -- To that contains encoded characters while base 64 is not needed as all symbols are 7bit
 -- Regexp that checks that To header is encoded with base64 (search in raw headers)
@@ -110,13 +189,23 @@ local to_encoded_b64 = 'To=/\\=\\?\\S+\\?B\\?/iX'
 -- To contains only 7bit characters (parsed headers are used)
 local to_needs_mime = 'To=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/Hr'
 -- Final rule
-reconf['TO_EXCESS_BASE64'] = string.format('%s & !%s', to_encoded_b64, to_needs_mime)
+reconf['TO_EXCESS_BASE64'] = {
+  re = string.format('%s & !%s', to_encoded_b64, to_needs_mime),
+  score = 1.5,
+  description = 'To that contains encoded characters while base 64 is not needed as all symbols are 7bit',
+  group = 'excessb64'
+}
 
 -- To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit
 -- Regexp that checks that To header is encoded with quoted-printable (search in raw headers)
 local to_encoded_qp = 'To=/\\=\\?\\S+\\?Q\\?/iX'
 -- Final rule
-reconf['TO_EXCESS_QP'] = string.format('%s & !%s', to_encoded_qp, to_needs_mime)
+reconf['TO_EXCESS_QP'] = {
+  re = string.format('%s & !%s', to_encoded_qp, to_needs_mime),
+  score = 1.2,
+  description = 'To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit',
+  group = 'excessqp'
+}
 
 -- Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit
 -- Regexp that checks that Reply-To header is encoded with base64 (search in raw headers)
@@ -124,13 +213,23 @@ local replyto_encoded_b64 = 'Reply-To=/\\=\\?\\S+\\?B\\?/iX'
 -- Reply-To contains only 7bit characters (parsed headers are used)
 local replyto_needs_mime = 'Reply-To=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/Hr'
 -- Final rule
-reconf['REPLYTO_EXCESS_BASE64'] = string.format('%s & !%s', replyto_encoded_b64, replyto_needs_mime)
+reconf['REPLYTO_EXCESS_BASE64'] = {
+  re = string.format('%s & !%s', replyto_encoded_b64, replyto_needs_mime),
+  score = 1.5,
+  description = 'Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit',
+  group = 'excessb64'
+}
 
 -- Reply-To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit
 -- Regexp that checks that Reply-To header is encoded with quoted-printable (search in raw headers)
 local replyto_encoded_qp = 'Reply-To=/\\=\\?\\S+\\?Q\\?/iX'
 -- Final rule
-reconf['REPLYTO_EXCESS_QP'] = string.format('%s & !%s', replyto_encoded_qp, replyto_needs_mime)
+reconf['REPLYTO_EXCESS_QP'] = {
+  re = string.format('%s & !%s', replyto_encoded_qp, replyto_needs_mime),
+  score = 1.2,
+  description = 'Reply-To that contains encoded characters while quoted-printable is not needed as all symbols are 7bit',
+  group = 'excessqp'
+}
 
 -- Cc that contains encoded characters while base 64 is not needed as all symbols are 7bit
 -- Regexp that checks that Cc header is encoded with base64 (search in raw headers)
@@ -138,13 +237,23 @@ local cc_encoded_b64 = 'Cc=/\\=\\?\\S+\\?B\\?/iX'
 -- Co contains only 7bit characters (parsed headers are used)
 local cc_needs_mime = 'Cc=/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\xff]/Hr'
 -- Final rule
-reconf['CC_EXCESS_BASE64'] = string.format('%s & !%s', cc_encoded_b64, cc_needs_mime)
+reconf['CC_EXCESS_BASE64'] = {
+  re = string.format('%s & !%s', cc_encoded_b64, cc_needs_mime),
+  score = 1.5,
+  description = 'Cc that contains encoded characters while base 64 is not needed as all symbols are 7bit',
+  group = 'excessb64'
+}
 
 -- Cc that contains encoded characters while quoted-printable is not needed as all symbols are 7bit
 -- Regexp that checks that Cc header is encoded with quoted-printable (search in raw headers)
 local cc_encoded_qp = 'Cc=/\\=\\?\\S+\\?Q\\?/iX'
 -- Final rule
-reconf['CC_EXCESS_QP'] = string.format('%s & !%s', cc_encoded_qp, cc_needs_mime)
+reconf['CC_EXCESS_QP'] = {
+  re = string.format('%s & !%s', cc_encoded_qp, cc_needs_mime),
+  score = 1.2,
+  description = 'Cc that contains encoded characters while quoted-printable is not needed as all symbols are 7bit',
+  group = 'excessqp'
+}
 
 
 -- Detect forged outlook headers
@@ -182,8 +291,13 @@ local forged_outlook_dollars = string.format('(%s & !%s & !%s & !%s & !%s & !%s)
 local fmo_excl_o3416 = 'X-Mailer=/^Microsoft Outlook, Build 10.0.3416$/H'
 local fmo_excl_oe3790 = 'X-Mailer=/^Microsoft Outlook Express 6.00.3790.3959$/H'
 -- Summary rule for forged outlook
-reconf['FORGED_MUA_OUTLOOK'] = string.format('(%s | %s) & !%s & !%s & !%s',
-                                       forged_oe, forged_outlook_dollars, fmo_excl_o3416, fmo_excl_oe3790, vista_msgid)
+reconf['FORGED_MUA_OUTLOOK'] = {
+  re = string.format('(%s | %s) & !%s & !%s & !%s',
+      forged_oe, forged_outlook_dollars, fmo_excl_o3416, fmo_excl_oe3790, vista_msgid),
+  score = 3.0,
+  description = 'Forged outlook MUA',
+  group = 'mua'
+}
 
 -- HTML outlook signs
 local mime_html = 'content_type_is_type(text) & content_type_is_subtype(/.?html/)'
@@ -191,20 +305,45 @@ local tag_exists_html = 'has_html_tag(html)'
 local tag_exists_head = 'has_html_tag(head)'
 local tag_exists_meta = 'has_html_tag(meta)'
 local tag_exists_body = 'has_html_tag(body)'
-reconf['FORGED_OUTLOOK_TAGS'] = string.format('!%s & %s & %s & !(%s & %s & %s & %s)',
-                                       yahoo_bulk, any_outlook_mua, mime_html, tag_exists_html, tag_exists_head,
-                                       tag_exists_meta, tag_exists_body)
+reconf['FORGED_OUTLOOK_TAGS'] = {
+  re = string.format('!%s & %s & %s & !(%s & %s & %s & %s)',
+      yahoo_bulk, any_outlook_mua, mime_html, tag_exists_html, tag_exists_head,
+      tag_exists_meta, tag_exists_body),
+  score = 2.1,
+  description = "Message pretends to be send from Outlook but has 'strange' tags",
+  group = 'header'
+}
 
 -- Forged OE/MSO boundary
-reconf['SUSPICIOUS_BOUNDARY']  = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_(00EBFFA4|0102FFA4|32C6FFA4|3302FFA4)\\.[A-Z\\d]{8}"[\\r\\n]*$/siX'
+reconf['SUSPICIOUS_BOUNDARY'] = {
+  re = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_(00EBFFA4|0102FFA4|32C6FFA4|3302FFA4)\\.[A-Z\\d]{8}"[\\r\\n]*$/siX',
+  score = 5.0,
+  description = 'Suspicious boundary in header Content-Type',
+  group = 'mua'
+}
 -- Forged OE/MSO boundary
-reconf['SUSPICIOUS_BOUNDARY2'] = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_(01C6527E)\\.[A-Z\\d]{8}"[\\r\\n]*$/siX'
+reconf['SUSPICIOUS_BOUNDARY2'] = {
+  re = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_(01C6527E)\\.[A-Z\\d]{8}"[\\r\\n]*$/siX',
+  score = 4.0,
+  description = 'Suspicious boundary in header Content-Type',
+  group = 'mua'
+}
 -- Forged OE/MSO boundary
-reconf['SUSPICIOUS_BOUNDARY3'] = 'Content-Type=/^\\s*multipart.+boundary="-----000-00\\d\\d-01C[\\dA-F]{5}-[\\dA-F]{8}"[\\r\\n]*$/siX'
+reconf['SUSPICIOUS_BOUNDARY3'] = {
+  re = 'Content-Type=/^\\s*multipart.+boundary="-----000-00\\d\\d-01C[\\dA-F]{5}-[\\dA-F]{8}"[\\r\\n]*$/siX',
+  score = 3.0,
+  description = 'Suspicious boundary in header Content-Type',
+  group = 'mua'
+}
 -- Forged OE/MSO boundary
 local suspicious_boundary_01C4 = 'Content-Type=/^\\s*multipart.+boundary="----=_NextPart_000_[A-Z\\d]{4}_01C4[\\dA-F]{4}\\.[A-Z\\d]{8}"[\\r\\n]*$/siX'
 local suspicious_boundary_01C4_date    = 'Date=/^\\s*\\w\\w\\w,\\s+\\d+\\s+\\w\\w\\w 20(0[56789]|1\\d)/'
-reconf['SUSPICIOUS_BOUNDARY4'] = string.format('(%s) & (%s)', suspicious_boundary_01C4, suspicious_boundary_01C4_date)
+reconf['SUSPICIOUS_BOUNDARY4'] = {
+  re = string.format('(%s) & (%s)', suspicious_boundary_01C4, suspicious_boundary_01C4_date),
+  score = 4.0,
+  description = 'Suspicious boundary in header Content-Type',
+  group = 'mua'
+}
 
 -- Detect forged The Bat! headers
 -- The Bat! X-Mailer header
@@ -214,10 +353,19 @@ local thebat_msgid_common = 'Message-ID=/^<?\\d+\\.\\d+\\@\\S+>?$/mH'
 -- Correct The Bat! Message-ID template
 local thebat_msgid = 'Message-ID=/^<?\\d+\\.(19[789]\\d|20\\d\\d)(0\\d|1[012])([012]\\d|3[01])([0-5]\\d)([0-5]\\d)([0-5]\\d)\\@\\S+>?/mH'
 -- Summary rule for forged The Bat! Message-ID header
-reconf['FORGED_MUA_THEBAT_MSGID'] = string.format('(%s) & !(%s) & (%s) & !(%s)', thebat_mua_any, thebat_msgid, thebat_msgid_common, unusable_msgid)
+reconf['FORGED_MUA_THEBAT_MSGID'] = {
+  re = string.format('(%s) & !(%s) & (%s) & !(%s)', thebat_mua_any, thebat_msgid, thebat_msgid_common, unusable_msgid),
+  score = 4.0,
+  description = 'Message pretends to be send from The Bat! but has forged Message-ID',
+  group = 'mua'
+}
 -- Summary rule for forged The Bat! Message-ID header with unknown template
-reconf['FORGED_MUA_THEBAT_MSGID_UNKNOWN'] = string.format('(%s) & !(%s) & !(%s) & !(%s)', thebat_mua_any, thebat_msgid, thebat_msgid_common, unusable_msgid)
-
+reconf['FORGED_MUA_THEBAT_MSGID_UNKNOWN'] = {
+  re = string.format('(%s) & !(%s) & !(%s) & !(%s)', thebat_mua_any, thebat_msgid, thebat_msgid_common, unusable_msgid),
+  score = 3.0,
+  description = 'Message pretends to be send from The Bat! but has forged Message-ID',
+  group = 'mua'
+}
 
 -- Detect forged KMail headers
 -- KMail User-Agent header
@@ -235,9 +383,19 @@ function kmail_msgid (task)
        return false
 end
 -- Summary rule for forged KMail Message-ID header
-reconf['FORGED_MUA_KMAIL_MSGID'] = string.format('(%s) & (%s) & !(%s) & !(%s)', kmail_mua, kmail_msgid_common, 'kmail_msgid', unusable_msgid)
+reconf['FORGED_MUA_KMAIL_MSGID'] = {
+  re = string.format('(%s) & (%s) & !(%s) & !(%s)', kmail_mua, kmail_msgid_common, 'kmail_msgid', unusable_msgid),
+  score = 3.0,
+  description = 'Message pretends to be send from KMail but has forged Message-ID',
+  group = 'mua'
+}
 -- Summary rule for forged KMail Message-ID header with unknown template
-reconf['FORGED_MUA_KMAIL_MSGID_UNKNOWN'] = string.format('(%s) & !(%s) & !(%s)', kmail_mua, kmail_msgid_common, unusable_msgid)
+reconf['FORGED_MUA_KMAIL_MSGID_UNKNOWN'] = {
+  re = string.format('(%s) & !(%s) & !(%s)', kmail_mua, kmail_msgid_common, unusable_msgid),
+  score = 2.5,
+  description = 'Message pretends to be send from KMail but has forged Message-ID',
+  group = 'mua'
+}
 
 -- Detect forged Opera Mail headers
 -- Opera Mail User-Agent header
@@ -249,10 +407,19 @@ local suspicious_opera10w_mua = 'User-Agent=/^\\s*Opera Mail\\/10\\.\\d+ \\(Wind
 -- Suspicious Opera Mail Message-ID, apparently from KMail
 local suspicious_opera10w_msgid = 'Message-Id=/^<?2009\\d{8}\\.\\d+\\.\\S+\\@\\S+?>$/H'
 -- Summary rule for forged Opera Mail User-Agent header and Message-ID header from KMail
-reconf['SUSPICIOUS_OPERA_10W_MSGID'] = string.format('(%s) & (%s)', suspicious_opera10w_mua, suspicious_opera10w_msgid)
+reconf['SUSPICIOUS_OPERA_10W_MSGID'] = {
+  re = string.format('(%s) & (%s)', suspicious_opera10w_mua, suspicious_opera10w_msgid),
+  score = 4.0,
+  description = 'Message pretends to be send from suspicious Opera Mail/10.x (Windows) but has forged Message-ID, apparently from KMail',
+  group = 'mua'
+}
 -- Summary rule for forged Opera Mail Message-ID header
-reconf['FORGED_MUA_OPERA_MSGID'] = string.format('(%s) & !(%s) & !(%s) & !(%s)', opera1x_mua, opera1x_msgid, reconf['SUSPICIOUS_OPERA_10W_MSGID'], unusable_msgid)
-
+reconf['FORGED_MUA_OPERA_MSGID'] = {
+  re = string.format('(%s) & !(%s) & !(%s) & !(%s)', opera1x_mua, opera1x_msgid, reconf['SUSPICIOUS_OPERA_10W_MSGID'], unusable_msgid),
+  score = 4.0,
+  description = 'Message pretends to be send from Opera Mail but has forged Message-ID',
+  group = 'mua'
+}
 
 -- Detect forged Mozilla Mail/Thunderbird/Seamonkey headers
 -- Mozilla based X-Mailer
@@ -265,20 +432,56 @@ local mozilla_msgid_common        = 'Message-ID=/^\\s*<[\\dA-F]{8}\\.\\d{1,7}\\@([^>\\.
 local mozilla_msgid_common_sec = 'Message-ID=/^\\s*<[\\da-f]{8}-([\\da-f]{4}-){3}[\\da-f]{12}\\@([^>\\.]+\\.)+[^>\\.]+>$/H'
 local mozilla_msgid            = 'Message-ID=/^\\s*<(3[3-9A-F]|4[\\dA-F]|5[\\dA-F])[\\dA-F]{6}\\.(\\d0){1,4}\\d\\@([^>\\.]+\\.)+[^>\\.]+>$/H'
 -- Summary rule for forged Mozilla Mail Message-ID header
-reconf['FORGED_MUA_MOZILLA_MAIL_MSGID'] = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_mozilla, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
-reconf['FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN'] = string.format('(%s) & !(%s) & !(%s) & !(%s)', user_agent_mozilla, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
+reconf['FORGED_MUA_MOZILLA_MAIL_MSGID'] = {
+  re = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_mozilla, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+  score = 4.0,
+  description = 'Message pretends to be send from Mozilla Mail but has forged Message-ID',
+  group = 'mua'
+}
+reconf['FORGED_MUA_MOZILLA_MAIL_MSGID_UNKNOWN'] = {
+  re = string.format('(%s) & !(%s) & !(%s) & !(%s)', user_agent_mozilla, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+  score = 2.5,
+  description = 'Message pretends to be send from Mozilla Mail but has forged Message-ID',
+  group = 'mua'
+}
+
 -- Summary rule for forged Thunderbird Message-ID header
-reconf['FORGED_MUA_THUNDERBIRD_MSGID'] = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_thunderbird, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
-reconf['FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN'] = string.format('(%s) & !((%s) | (%s)) & !(%s) & !(%s)', user_agent_thunderbird, mozilla_msgid_common, mozilla_msgid_common_sec, mozilla_msgid, unusable_msgid)
+reconf['FORGED_MUA_THUNDERBIRD_MSGID'] = {
+  re = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_thunderbird, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+  score = 4.0,
+  description = 'Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID',
+  group = 'mua'
+}
+reconf['FORGED_MUA_THUNDERBIRD_MSGID_UNKNOWN'] = {
+  re = string.format('(%s) & !((%s) | (%s)) & !(%s) & !(%s)', user_agent_thunderbird, mozilla_msgid_common, mozilla_msgid_common_sec, mozilla_msgid, unusable_msgid),
+  score = 2.5,
+  description = 'Forged mail pretending to be from Mozilla Thunderbird but has forged Message-ID',
+  group = 'mua'
+}
 -- Summary rule for forged Seamonkey Message-ID header
-reconf['FORGED_MUA_SEAMONKEY_MSGID'] = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_seamonkey, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
-reconf['FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN'] = string.format('(%s) & !(%s) & !(%s) & !(%s)', user_agent_seamonkey, mozilla_msgid_common, mozilla_msgid, unusable_msgid)
+reconf['FORGED_MUA_SEAMONKEY_MSGID'] = {
+  re = string.format('(%s) & (%s) & !(%s) & !(%s)', user_agent_seamonkey, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+  score = 4.0,
+  description = 'Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID',
+  group = 'mua'
+}
+reconf['FORGED_MUA_SEAMONKEY_MSGID_UNKNOWN'] = {
+  re = string.format('(%s) & !(%s) & !(%s) & !(%s)', user_agent_seamonkey, mozilla_msgid_common, mozilla_msgid, unusable_msgid),
+  score = 2.5,
+  description = 'Forged mail pretending to be from Mozilla Seamonkey but has forged Message-ID',
+  group = 'mua'
+}
 
 
 -- Message id validity
 local sane_msgid = 'Message-Id=/^<?[^<>\\\\ \\t\\n\\r\\x0b\\x80-\\xff]+\\@[^<>\\\\ \\t\\n\\r\\x0b\\x80-\\xff]+>?\\s*$/H'
 local msgid_comment = 'Message-Id=/\\(.*\\)/H'
-reconf['INVALID_MSGID'] = string.format('(%s) & !((%s) | (%s))', has_mid, sane_msgid, msgid_comment)
+reconf['INVALID_MSGID'] = {
+  re = string.format('(%s) & !((%s) | (%s))', has_mid, sane_msgid, msgid_comment),
+  score = 1.7,
+  description = 'Message id is incorrect',
+  group = 'header'
+}
 
 
 -- Only Content-Type header without other MIME headers
@@ -287,17 +490,32 @@ local cte = 'header_exists(Content-Transfer-Encoding)'
 local ct = 'header_exists(Content-Type)'
 local mime_version = 'raw_header_exists(MIME-Version)'
 local ct_text_plain = 'content_type_is_type(text) & content_type_is_subtype(plain)'
-reconf['MIME_HEADER_CTYPE_ONLY'] = string.format('!(%s) & !(%s) & (%s) & !(%s) & !(%s)', cd, cte, ct, mime_version, ct_text_plain)
+reconf['MIME_HEADER_CTYPE_ONLY'] = {
+  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'
+}
 
 
 -- Forged Exchange messages
 local msgid_dollars_ok = 'Message-Id=/[0-9a-f]{4,}\\$[0-9a-f]{4,}\\$[0-9a-f]{4,}\\@\\S+/H'
 local mimeole_ms = 'X-MimeOLE=/^Produced By Microsoft MimeOLE/H'
 local rcvd_with_exchange = 'Received=/with Microsoft Exchange Server/H'
-reconf['RATWARE_MS_HASH'] = string.format('(%s) & !(%s) & !(%s)', msgid_dollars_ok, mimeole_ms, rcvd_with_exchange)
+reconf['RATWARE_MS_HASH'] = {
+  re = string.format('(%s) & !(%s) & !(%s)', msgid_dollars_ok, mimeole_ms, rcvd_with_exchange),
+  score = 2.0,
+  description = 'Forged Exchange messages',
+  group = 'header'
+}
 
 -- Reply-type in content-type
-reconf['STOX_REPLY_TYPE'] = 'Content-Type=/text\\/plain; .* reply-type=original/H'
+reconf['STOX_REPLY_TYPE'] = {
+  re = 'Content-Type=/text\\/plain; .* reply-type=original/H',
+  score = 1.0,
+  description = 'Reply-type in content-type',
+  group = 'header'
+}
 
 -- Fake Verizon headers
 local fhelo_verizon = 'X-Spam-Relays-Untrusted=/^[^\\]]+ helo=[^ ]+verizon\\.net /iH'
@@ -308,7 +526,12 @@ reconf['FM_FAKE_HELO_VERIZON'] = string.format('(%s) & !(%s)', fhelo_verizon, fh
 local at_yahoo_msgid = 'Message-Id=/\\@yahoo\\.com\\b/iH'
 local at_yahoogroups_msgid = 'Message-Id=/\\@yahoogroups\\.com\\b/iH'
 local from_yahoo_com = 'From=/\\@yahoo\\.com\\b/iH'
-reconf['FORGED_MSGID_YAHOO'] = string.format('(%s) & !(%s)', at_yahoo_msgid, from_yahoo_com)
+reconf['FORGED_MSGID_YAHOO'] = {
+  re = string.format('(%s) & !(%s)', at_yahoo_msgid, from_yahoo_com),
+  score = 2.0,
+  description = 'Forged yahoo msgid',
+  group = 'header'
+}
 local r_from_yahoo_groups = 'From=/rambler.ru\\@returns\\.groups\\.yahoo\\.com\\b/iH'
 local r_from_yahoo_groups_ro = 'From=/ro.ru\\@returns\\.groups\\.yahoo\\.com\\b/iH'
 
@@ -317,18 +540,33 @@ local thebat_mua_v1 = 'X-Mailer=/^The Bat! \\(v1\\./H'
 local ctype_has_boundary = 'Content-Type=/boundary/iH'
 local bat_boundary = 'Content-Type=/boundary=\\"?-{10}/H'
 local mailman_21 = 'X-Mailman-Version=/\\d/H'
-reconf['FORGED_MUA_THEBAT_BOUN'] = string.format('(%s) & (%s) & !(%s) & !(%s)', thebat_mua_v1, ctype_has_boundary, bat_boundary, mailman_21)
+reconf['FORGED_MUA_THEBAT_BOUN'] = {
+  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'
+}
 
 -- Two received headers with ip addresses
 local double_ip_spam_1 = 'Received=/from \\[\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\] by \\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} with/H'
 local double_ip_spam_2 = 'Received=/from\\s+\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\s+by\\s+\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3};/H'
-reconf['RCVD_DOUBLE_IP_SPAM'] = string.format('(%s) | (%s)', double_ip_spam_1, double_ip_spam_2)
+reconf['RCVD_DOUBLE_IP_SPAM'] = {
+  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'
+}
 
 -- Quoted reply-to from yahoo (seems to be forged)
 local repto_quote = 'Reply-To=/\\".*\\"\\s*\\</H'
 local from_yahoo_com = 'From=/\\@yahoo\\.com\\b/iH'
 local at_yahoo_msgid = 'Message-Id=/\\@yahoo\\.com\\b/iH'
-reconf['REPTO_QUOTE_YAHOO'] = string.format('(%s) & ((%s) | (%s))', repto_quote, from_yahoo_com, at_yahoo_msgid)
+reconf['REPTO_QUOTE_YAHOO'] = {
+  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'
+}
 
 -- MUA definitions
 local xm_gnus = 'X-Mailer=/^Gnus v/H'
@@ -349,18 +587,28 @@ local subj_re = 'Subject=/^R[eE]:/H'
 local has_ref = 'header_exists(References)'
 local missing_ref = string.format('!(%s)', has_ref)
 -- Fake reply (has RE in subject, but has not References header)
-reconf['FAKE_REPLY_C'] = string.format('(%s) & (%s) & (%s) & !(%s)', subj_re, missing_ref, no_inr_yes_ref, xm_msoe6)
+reconf['FAKE_REPLY_C'] = {
+  re = string.format('(%s) & (%s) & (%s) & !(%s)', subj_re, missing_ref, no_inr_yes_ref, xm_msoe6),
+  score = 6.0,
+  description = 'Fake reply (has RE in subject, but has not References header)',
+  group = 'subject'
+}
 
 -- Mime-OLE is needed but absent (e.g. fake Outlook or fake Ecxchange)
 local has_msmail_pri = 'header_exists(X-MSMail-Priority)'
 local has_mimeole = 'header_exists(X-MimeOLE)'
 local has_squirrelmail_in_mailer = 'X-Mailer=/SquirrelMail\\b/H'
 local has_office12145_in_mailer = 'X-Mailer=/^Microsoft (?:Office )?Outlook 1[245]\\.0/'
-reconf['MISSING_MIMEOLE'] = string.format('(%s) & !(%s) & !(%s) & !(%s)',
-  has_msmail_pri,
-  has_mimeole,
-  has_squirrelmail_in_mailer,
-  has_office12145_in_mailer)
+reconf['MISSING_MIMEOLE'] = {
+  re = string.format('(%s) & !(%s) & !(%s) & !(%s)',
+      has_msmail_pri,
+      has_mimeole,
+      has_squirrelmail_in_mailer,
+      has_office12145_in_mailer),
+  score = 2.0,
+  description = 'Mime-OLE is needed but absent (e.g. fake Outlook or fake Exchange)',
+  group = 'header'
+}
 
 -- Header delimiters
 local yandex_from = 'From=/\\@(yandex\\.ru|yandex\\.net|ya\\.ru)/iX'
@@ -375,11 +623,36 @@ function check_header_delimiter_tab(task, header_name)
        end
        return false
 end
-reconf['HEADER_FROM_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(From)', yandex)
-reconf['HEADER_TO_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(To)', yandex)
-reconf['HEADER_CC_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Cc)', yandex)
-reconf['HEADER_REPLYTO_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Reply-To)', yandex)
-reconf['HEADER_DATE_DELIMITER_TAB'] = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(Date)', yandex)
+reconf['HEADER_FROM_DELIMITER_TAB'] = {
+  re = string.format('(%s) & !(%s)', 'check_header_delimiter_tab(From)', yandex),
+  score = 1.0,
+  description = 'Header From begins with tab',
+  group = 'header'
+}
+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'
+}
+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'
+}
+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'
+}
+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'
+}
 -- Empty delimiters between header names and header values
 function check_header_delimiter_empty(task, header_name)
        for _,rh in ipairs(task:get_header_full(header_name)) do
@@ -387,21 +660,56 @@ function check_header_delimiter_empty(task, header_name)
        end
        return false
 end
-reconf['HEADER_FROM_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(From)')
-reconf['HEADER_TO_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(To)')
-reconf['HEADER_CC_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(Cc)')
-reconf['HEADER_REPLYTO_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(Reply-To)')
-reconf['HEADER_DATE_EMPTY_DELIMITER'] = string.format('(%s)', 'check_header_delimiter_empty(Date)')
+reconf['HEADER_FROM_EMPTY_DELIMITER'] = {
+  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'
+}
+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'
+}
+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'
+}
+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'
+}
+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'
+}
 
 -- Definitions of received headers regexp
-reconf['RCVD_ILLEGAL_CHARS'] = 'Received=/[\\x80-\\xff]/X'
+reconf['RCVD_ILLEGAL_CHARS'] = {
+  re = 'Received=/[\\x80-\\xff]/X',
+  score = 4.0,
+  description = 'Header Received has raw illegal character',
+  group = 'header'
+}
 
 local MAIL_RU_Return_Path      = 'Return-path=/^\\s*<.+\\@mail\\.ru>$/iX'
 local MAIL_RU_X_Envelope_From  = 'X-Envelope-From=/^\\s*<.+\\@mail\\.ru>$/iX'
 local MAIL_RU_From             = 'From=/\\@mail\\.ru>?$/iX'
 local MAIL_RU_Received         = 'Received=/from mail\\.ru \\(/mH'
 
-reconf['FAKE_RECEIVED_mail_ru'] = string.format('(%s) & !(((%s) | (%s)) & (%s))', MAIL_RU_Received, MAIL_RU_Return_Path, MAIL_RU_X_Envelope_From, MAIL_RU_From)
+reconf['FAKE_RECEIVED_mail_ru'] = {
+  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'
+}
 
 local GMAIL_COM_Return_Path    = 'Return-path=/^\\s*<.+\\@gmail\\.com>$/iX'
 local GMAIL_COM_X_Envelope_From        = 'X-Envelope-From=/^\\s*<.+\\@gmail\\.com>$/iX'
@@ -421,19 +729,54 @@ local RECEIVED_smtp_yandex_ru_7   = 'Received=/from \\S+ \\(HELO smtp\\.yandex\\.r
 local RECEIVED_smtp_yandex_ru_8        = 'Received=/from \\S+ \\(HELO smtp\\.yandex\\.ru\\) \\(\\d+\\.\\d+\\.\\d+\\.\\d+\\)/iX'
 local RECEIVED_smtp_yandex_ru_9        = 'Received=/from \\S+ \\(\\[\\d+\\.\\d+\\.\\d+\\.\\d+\\] helo=smtp\\.yandex\\.ru\\)/iX'
 
-reconf['FAKE_RECEIVED_smtp_yandex_ru'] = 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)
+reconf['FAKE_RECEIVED_smtp_yandex_ru'] = {
+  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'
+}
 
-reconf['FORGED_GENERIC_RECEIVED'] =    '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'
+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'
+}
 
-reconf['FORGED_GENERIC_RECEIVED2'] =   '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'
+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'
+}
 
-reconf['FORGED_GENERIC_RECEIVED3'] =   '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'
+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'
+}
 
-reconf['FORGED_GENERIC_RECEIVED4'] =   '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'
+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'
+}
 
-reconf['FORGED_GENERIC_RECEIVED5'] = 'Received=/\\s*from \\[(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\].*\\n(.+\\n)*\\s*from \\1 by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0$/X'
+reconf['FORGED_GENERIC_RECEIVED5'] = {
+  re = 'Received=/\\s*from \\[(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\].*\\n(.+\\n)*\\s*from \\1 by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0$/X',
+  score = 4.6,
+  description = 'Forged generic Received',
+  group = 'header'
+}
 
-reconf['INVALID_POSTFIX_RECEIVED'] =   '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'
+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'
+}
 
 reconf['X_PHP_EVAL'] = {
   re = "X-PHP-Originating-Script=/ : eval\\(\\)'d code$/X",