Port Spam filter to Rust - part 2
This commit is contained in:
@@ -1,158 +0,0 @@
|
||||
let "from_count" "count(header.from[*].raw)";
|
||||
let "service_accounts" "['www-data', 'anonymous', 'ftp', 'apache', 'nobody', 'guest', 'nginx', 'web', 'www']";
|
||||
|
||||
if eval "from_count > 0" {
|
||||
let "from_raw" "to_lowercase(header.from.raw)";
|
||||
|
||||
if eval "from_count > 1" {
|
||||
let "t.MULTIPLE_FROM" "1";
|
||||
}
|
||||
|
||||
if eval "is_email(from_addr)" {
|
||||
if eval "contains(service_accounts, from_local)" {
|
||||
let "t.FROM_SERVICE_ACCT" "1";
|
||||
}
|
||||
if eval "starts_with(from_domain, 'www.')" {
|
||||
let "t.WWW_DOT_DOMAIN" "1";
|
||||
}
|
||||
|
||||
if eval "key_exists('spam-free', from_domain_sld)" {
|
||||
let "t.FREEMAIL_FROM" "1";
|
||||
} elsif eval "key_exists('spam-disposable', from_domain_sld)" {
|
||||
let "t.DISPOSABLE_FROM" "1";
|
||||
}
|
||||
} else {
|
||||
let "t.FROM_INVALID" "1";
|
||||
}
|
||||
|
||||
if eval "is_empty(from_name)" {
|
||||
let "t.FROM_NO_DN" "1";
|
||||
} elsif eval "eq_ignore_case(from_addr, from_name)" {
|
||||
let "t.FROM_DN_EQ_ADDR" "1";
|
||||
} else {
|
||||
if eval "!t.FROM_INVALID" {
|
||||
let "t.FROM_HAS_DN" "1";
|
||||
}
|
||||
|
||||
if eval "is_email(from_name)" {
|
||||
let "from_name_sld" "domain_part(email_part(from_name, 'domain'), 'sld')";
|
||||
if eval "(!t.FROM_INVALID && from_domain_sld != from_name_sld) ||
|
||||
(!is_empty(envelope.from) && envfrom_domain_sld != from_name_sld) ||
|
||||
(is_empty(envelope.from) && helo_domain_sld != from_name_sld)" {
|
||||
let "t.SPOOF_DISPLAY_NAME" "1";
|
||||
} else {
|
||||
let "t.FROM_NEQ_DISPLAY_NAME" "1";
|
||||
}
|
||||
} else {
|
||||
if eval "contains(from_name, 'mr. ') || contains(from_name, 'ms. ') || contains(from_name, 'mrs. ') || contains(from_name, 'dr. ')" {
|
||||
let "t.FROM_NAME_HAS_TITLE" "1";
|
||||
}
|
||||
if eval "contains(header.from.name, ' ')" {
|
||||
let "t.FROM_NAME_EXCESS_SPACE" "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if eval "is_empty(envelope.from) &&
|
||||
(from_local == 'postmaster' ||
|
||||
from_local == 'mailer-daemon' ||
|
||||
from_local == 'root')" {
|
||||
let "t.FROM_BOUNCE" "1";
|
||||
}
|
||||
|
||||
if eval "(!is_empty(envelope.from) &&
|
||||
eq_ignore_case(from_addr, envelope.from)) ||
|
||||
(t.FROM_BOUNCE &&
|
||||
!is_empty(from_domain) &&
|
||||
from_domain_sld == helo_domain_sld)" {
|
||||
let "t.FROM_EQ_ENVFROM" "1";
|
||||
} elsif eval "!t.FROM_INVALID" {
|
||||
let "t.FORGED_SENDER" "1";
|
||||
let "t.FROM_NEQ_ENVFROM" "1";
|
||||
}
|
||||
|
||||
if eval "contains(from_local, '+')" {
|
||||
let "t.TAGGED_FROM" "1";
|
||||
}
|
||||
|
||||
if eval "count(recipients_to) + count(recipients_cc) == 1" {
|
||||
if eval "eq_ignore_case(recipients_to[0], from_addr)" {
|
||||
let "t.TO_EQ_FROM" "1";
|
||||
} elsif eval "eq_ignore_case(email_part(recipients_to[0], 'domain'), from_domain)" {
|
||||
let "t.TO_DOM_EQ_FROM_DOM" "1";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "!is_ascii(from_raw)" {
|
||||
if eval "!env.param.smtputf8 && env.param.body != '8bitmime' && env.param.body != 'binarymime'" {
|
||||
let "t.FROM_NEEDS_ENCODING" "1";
|
||||
}
|
||||
if eval "!is_header_utf8_valid('From')" {
|
||||
let "t.INVALID_FROM_8BIT" "1";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "is_ascii(header.from) && contains(from_raw, '=?') && contains(from_raw, '?=')" {
|
||||
if eval "contains(from_raw, '?q?')" {
|
||||
# From header is unnecessarily encoded in quoted-printable
|
||||
let "t.FROM_EXCESS_QP" "1";
|
||||
} elsif eval "contains(from_raw, '?b?')" {
|
||||
# From header is unnecessarily encoded in base64
|
||||
let "t.FROM_EXCESS_BASE64" "1";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "!is_empty(from_name) && !is_empty(from_addr) && !contains(from_raw, ' <')" {
|
||||
let "t.R_NO_SPACE_IN_FROM" "1";
|
||||
}
|
||||
|
||||
# Read confirmation address is different to from address
|
||||
let "crt" "header.X-Confirm-Reading-To.addr";
|
||||
if eval "!is_empty(crt) && !eq_ignore_case(from_addr, crt)" {
|
||||
let "t.HEADER_RCONFIRM_MISMATCH" "1";
|
||||
}
|
||||
} else {
|
||||
let "t.MISSING_FROM" "1";
|
||||
}
|
||||
|
||||
if eval "!is_empty(envelope.from)" {
|
||||
if eval "is_email(envelope.from)" {
|
||||
if eval "contains(service_accounts, envfrom_local)" {
|
||||
let "t.ENVFROM_SERVICE_ACCT" "1";
|
||||
}
|
||||
} else {
|
||||
let "t.ENVFROM_INVALID" "1";
|
||||
}
|
||||
|
||||
if eval "!is_empty(envfrom_domain_sld)" {
|
||||
if eval "key_exists('spam-free', envfrom_domain_sld)" {
|
||||
let "t.FREEMAIL_ENVFROM" "1";
|
||||
} elsif eval "key_exists('spam-disposable', envfrom_domain_sld)" {
|
||||
let "t.DISPOSABLE_ENVFROM" "1";
|
||||
}
|
||||
|
||||
# Mail from no resolve to A or MX
|
||||
if eval "!dns_exists(envfrom_domain, 'mx') && !dns_exists(envfrom_domain, 'ip')" {
|
||||
let "t.FROMHOST_NORES_A_OR_MX" "1";
|
||||
}
|
||||
}
|
||||
|
||||
# Read confirmation address is different to return path
|
||||
let "dnt" "header.Disposition-Notification-To.addr";
|
||||
if eval "!is_empty(dnt) && !eq_ignore_case(envelope.from, dnt)" {
|
||||
let "t.HEADER_FORGED_MDN" "1";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "!t.FROM_SERVICE_ACCT &&
|
||||
(contains_ignore_case(service_accounts, email_part(rto_addr, 'local')) ||
|
||||
contains_ignore_case(service_accounts, email_part(header.sender.addr, 'local')))" {
|
||||
let "t.FROM_SERVICE_ACCT" "1";
|
||||
}
|
||||
|
||||
if eval "!t.WWW_DOT_DOMAIN &&
|
||||
(contains_ignore_case(rto_addr, '@www.') ||
|
||||
contains_ignore_case(header.sender.addr, '@www.'))" {
|
||||
let "t.WWW_DOT_DOMAIN" "1";
|
||||
}
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
|
||||
let "to_raw" "to_lowercase(header.to.raw)";
|
||||
if eval "!is_empty(to_raw)" {
|
||||
if eval "is_ascii(header.to) && contains(to_raw, '=?') && contains(to_raw, '?=')" {
|
||||
if eval "contains(to_raw, '?q?')" {
|
||||
# To header is unnecessarily encoded in quoted-printable
|
||||
let "t.TO_EXCESS_QP" "1";
|
||||
} elsif eval "contains(to_raw, '?b?')" {
|
||||
# To header is unnecessarily encoded in base64
|
||||
let "t.TO_EXCESS_BASE64" "1";
|
||||
}
|
||||
} elsif eval "!is_ascii(to_raw) && !env.param.smtputf8 && env.param.body != '8bitmime' && env.param.body != 'binarymime'" {
|
||||
# To needs encoding
|
||||
let "t.TO_NEEDS_ENCODING" "1";
|
||||
}
|
||||
} else {
|
||||
let "t.MISSING_TO" "1";
|
||||
}
|
||||
|
||||
let "rcpt_count" "count(recipients_clean)";
|
||||
|
||||
if eval "rcpt_count > 0" {
|
||||
if eval "rcpt_count == 1" {
|
||||
let "t.RCPT_COUNT_ONE" "1";
|
||||
} elsif eval "rcpt_count == 2" {
|
||||
let "t.RCPT_COUNT_TWO" "1";
|
||||
} elsif eval "rcpt_count == 3" {
|
||||
let "t.RCPT_COUNT_THREE" "1";
|
||||
} elsif eval "rcpt_count <= 5" {
|
||||
let "t.RCPT_COUNT_FIVE" "1";
|
||||
} elsif eval "rcpt_count <= 7" {
|
||||
let "t.RCPT_COUNT_SEVEN" "1";
|
||||
} elsif eval "rcpt_count <= 12" {
|
||||
let "t.RCPT_COUNT_TWELVE" "1";
|
||||
} else {
|
||||
let "t.RCPT_COUNT_GT_50" "1";
|
||||
}
|
||||
|
||||
let "rcpt_name" "to_lowercase(header.to:cc:bcc[*].name[*])";
|
||||
let "i" "count(recipients)";
|
||||
let "to_dn_count" "0";
|
||||
let "to_dn_eq_addr_count" "0";
|
||||
let "to_match_envrcpt" "0";
|
||||
|
||||
while "i != 0" {
|
||||
let "i" "i - 1";
|
||||
let "addr" "recipients[i]";
|
||||
|
||||
if eval "!is_empty(addr)" {
|
||||
let "name" "rcpt_name[i]";
|
||||
|
||||
if eval "!is_empty(name)" {
|
||||
if eval "name == addr" {
|
||||
let "to_dn_eq_addr_count" "to_dn_eq_addr_count + 1";
|
||||
} else {
|
||||
let "to_dn_count" "to_dn_count + 1";
|
||||
if eval "name == 'recipient' || name == 'recipients'" {
|
||||
let "t.TO_DN_RECIPIENTS" "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if eval "contains(envelope.to, addr)" {
|
||||
let "to_match_envrcpt" "to_match_envrcpt + 1";
|
||||
}
|
||||
|
||||
# Check if the local part is present in the subject
|
||||
let "local_part" "email_part(addr, 'local')";
|
||||
if eval "!is_empty(local_part)" {
|
||||
if eval "contains(subject_lc, addr)" {
|
||||
let "t.RCPT_ADDR_IN_SUBJECT" "1";
|
||||
} elsif eval "len(local_part) > 3 && contains(subject_lc, local_part)" {
|
||||
let "t.RCPT_LOCAL_IN_SUBJECT" "1";
|
||||
}
|
||||
|
||||
if eval "contains(local_part, '+')" {
|
||||
let "t.TAGGED_RCPT" "1";
|
||||
}
|
||||
}
|
||||
|
||||
# Check if it is an into to info
|
||||
if eval "!t.INFO_TO_INFO_LU &&
|
||||
local_part == 'info' &&
|
||||
from_local == 'info' &&
|
||||
header.List-Unsubscribe.exists" {
|
||||
let "t.INFO_TO_INFO_LU" "1";
|
||||
}
|
||||
|
||||
# Check for freemail or disposable domains
|
||||
let "domain" "domain_part(email_part(addr, 'domain'), 'sld')";
|
||||
if eval "!is_empty(domain)" {
|
||||
if eval "key_exists('spam-free', domain)" {
|
||||
if eval "!t.FREEMAIL_TO && contains_ignore_case(recipients_to, addr)" {
|
||||
let "t.FREEMAIL_TO" "1";
|
||||
} elsif eval "!t.FREEMAIL_CC && contains_ignore_case(recipients_cc, addr)" {
|
||||
let "t.FREEMAIL_CC" "1";
|
||||
}
|
||||
} elsif eval "key_exists('spam-disposable', domain)" {
|
||||
if eval "!t.DISPOSABLE_TO && contains_ignore_case(recipients_to, addr)" {
|
||||
let "t.DISPOSABLE_TO" "1";
|
||||
} elsif eval "!t.DISPOSABLE_CC && contains_ignore_case(recipients_cc, addr)" {
|
||||
let "t.DISPOSABLE_CC" "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if eval "to_dn_count == 0 && to_dn_eq_addr_count == 0" {
|
||||
let "t.TO_DN_NONE" "1";
|
||||
} elsif eval "to_dn_count == rcpt_count" {
|
||||
let "t.TO_DN_ALL" "1";
|
||||
} elsif eval "to_dn_count > 0" {
|
||||
let "t.TO_DN_SOME" "1";
|
||||
}
|
||||
|
||||
if eval "to_dn_eq_addr_count == rcpt_count" {
|
||||
let "t.TO_DN_EQ_ADDR_ALL" "1";
|
||||
} elsif eval "to_dn_eq_addr_count > 0" {
|
||||
let "t.TO_DN_EQ_ADDR_SOME" "1";
|
||||
}
|
||||
|
||||
if eval "to_match_envrcpt == rcpt_count" {
|
||||
let "t.TO_MATCH_ENVRCPT_ALL" "1";
|
||||
} else {
|
||||
if eval "to_match_envrcpt > 0" {
|
||||
let "t.TO_MATCH_ENVRCPT_SOME" "1";
|
||||
}
|
||||
|
||||
if eval "is_empty(header.List-Unsubscribe:List-Id[*])" {
|
||||
let "i" "count(envelope.to)";
|
||||
while "i != 0" {
|
||||
let "i" "i - 1";
|
||||
let "env_rcpt" "envelope.to[i]";
|
||||
|
||||
if eval "!contains(recipients, env_rcpt) && env_rcpt != envelope.from" {
|
||||
let "t.FORGED_RECIPIENTS" "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Message from bounce and over 1 recipient
|
||||
if eval "rcpt_count > 1 &&
|
||||
(is_empty(envelope.from) ||
|
||||
envfrom_local == 'postmaster' ||
|
||||
envfrom_local == 'mailer-daemon')" {
|
||||
let "t.RCPT_BOUNCEMOREONE" "1";
|
||||
}
|
||||
|
||||
# Check for sorted recipients
|
||||
if eval "rcpt_count >= 7 && sort(recipients_clean, false) == recipients_clean" {
|
||||
let "t.SORTED_RECIPS" "1";
|
||||
}
|
||||
|
||||
# Check for suspiciously similar recipients
|
||||
if eval "!t.SORTED_RECIPS && rcpt_count => 5" {
|
||||
let "i" "rcpt_count";
|
||||
let "hits" "0";
|
||||
let "combinations" "0";
|
||||
|
||||
while "i" {
|
||||
let "i" "i - 1";
|
||||
let "j" "i";
|
||||
while "j" {
|
||||
let "j" "j - 1";
|
||||
let "a" "recipients_clean[i]";
|
||||
let "b" "recipients_clean[j]";
|
||||
|
||||
if eval "levenshtein_distance(email_part(a, 'local'), email_part(b, 'local')) < 3" {
|
||||
let "hits" "hits + 1";
|
||||
}
|
||||
|
||||
let "a" "email_part(a, 'domain')";
|
||||
let "b" "email_part(b, 'domain')";
|
||||
|
||||
if eval "a != b && levenshtein_distance(a, b) < 4" {
|
||||
let "hits" "hits + 1";
|
||||
}
|
||||
|
||||
let "combinations" "combinations + 1";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "hits / combinations > 0.65" {
|
||||
let "t.SUSPICIOUS_RECIPS" "1";
|
||||
}
|
||||
}
|
||||
|
||||
# Check for spaces in recipient addresses
|
||||
let "raw_to" "header.to:cc[*].raw";
|
||||
let "i" "len(raw_to)";
|
||||
while "i != 0" {
|
||||
let "i" "i - 1";
|
||||
let "raw_addr" "rsplit(raw_to[i], '<')[0]";
|
||||
if eval "contains(raw_addr, '>') && (starts_with(raw_addr, ' ' ) || ends_with(raw_addr, ' >'))" {
|
||||
let "t.TO_WRAPPED_IN_SPACES" "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
let "t.RCPT_COUNT_ZERO" "1";
|
||||
|
||||
if eval "contains(to_raw, 'undisclosed') && contains(to_raw, 'recipients')" {
|
||||
let "t.R_UNDISC_RCPT" "1";
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
let "rto_raw" "to_lowercase(header.reply-to.raw)";
|
||||
if eval "!is_empty(rto_raw)" {
|
||||
let "rto_name" "to_lowercase(header.reply-to.name)";
|
||||
|
||||
if eval "is_email(rto_addr)" {
|
||||
let "t.HAS_REPLYTO" "1";
|
||||
let "rto_domain_sld" "domain_part(email_part(rto_addr, 'domain'), 'sld')";
|
||||
|
||||
if eval "eq_ignore_case(header.reply-to, header.from)" {
|
||||
let "t.REPLYTO_EQ_FROM" "1";
|
||||
} else {
|
||||
if eval "rto_domain_sld == from_domain_sld" {
|
||||
let "t.REPLYTO_DOM_EQ_FROM_DOM" "1";
|
||||
} else {
|
||||
let "is_from_list" "!is_empty(header.List-Unsubscribe:List-Id:X-To-Get-Off-This-List:X-List:Auto-Submitted[*])";
|
||||
if eval "!is_from_list && contains_ignore_case(recipients_clean, rto_addr)" {
|
||||
let "t.REPLYTO_EQ_TO_ADDR" "1";
|
||||
} else {
|
||||
let "t.REPLYTO_DOM_NEQ_FROM_DOM" "1";
|
||||
}
|
||||
|
||||
if eval "!is_from_list &&
|
||||
!eq_ignore_case(from_addr, header.to.addr) &&
|
||||
!(count(envelope.to) == 1 && envelope.to[0] == from_addr)" {
|
||||
let "i" "count(envelope.to)";
|
||||
let "found_domain" "0";
|
||||
|
||||
while "i != 0" {
|
||||
let "i" "i - 1";
|
||||
|
||||
if eval "domain_part(email_part(envelope.to[i], 'domain'), 'sld') == from_domain_sld" {
|
||||
let "found_domain" "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if eval "!found_domain" {
|
||||
let "t.SPOOF_REPLYTO" "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if eval "!is_empty(rto_name) && eq_ignore_case(rto_name, header.from.name)" {
|
||||
let "t.REPLYTO_DN_EQ_FROM_DN" "1";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "rto_addr == envelope.from" {
|
||||
let "t.REPLYTO_ADDR_EQ_FROM" "1";
|
||||
}
|
||||
|
||||
if eval "key_exists('spam-free', rto_domain_sld)" {
|
||||
let "t.FREEMAIL_REPLYTO" "1";
|
||||
if eval "rto_domain_sld != from_domain_sld && key_exists('spam-free', from_domain_sld)" {
|
||||
let "t.FREEMAIL_REPLYTO_NEQ_FROM_DOM" "1";
|
||||
}
|
||||
} elsif eval "key_exists('spam-disposable', rto_domain_sld)" {
|
||||
let "t.DISPOSABLE_REPLYTO" "1";
|
||||
}
|
||||
|
||||
} else {
|
||||
let "t.REPLYTO_UNPARSABLE" "1";
|
||||
}
|
||||
|
||||
if eval "is_ascii(header.reply-to) && contains(rto_raw, '=?') && contains(rto_raw, '?=')" {
|
||||
if eval "contains(rto_raw, '?q?')" {
|
||||
# Reply-To header is unnecessarily encoded in quoted-printable
|
||||
let "t.REPLYTO_EXCESS_QP" "1";
|
||||
} elsif eval "contains(rto_raw, '?b?')" {
|
||||
# Reply-To header is unnecessarily encoded in base64
|
||||
let "t.REPLYTO_EXCESS_BASE64" "1";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "contains(rto_name, 'mr. ') || contains(rto_name, 'ms. ') || contains(rto_name, 'mrs. ') || contains(rto_name, 'dr. ')" {
|
||||
let "t.REPLYTO_EMAIL_HAS_TITLE" "1";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user