v0.3.10
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
if eval "!t.SPAM_TRAP && !t.TRUSTED_REPLY" {
|
||||
let "bayes_result" "bayes_classify('spamdb/token-lookup', body_and_subject)";
|
||||
|
||||
# Classification parameters
|
||||
# min_token_hits: 2
|
||||
# min_tokens: 11
|
||||
# min_prob_strength: 0.05
|
||||
# min_learns: 200
|
||||
|
||||
let "bayes_result" "bayes_classify('spamdb/token-lookup', body_and_subject, [2, 11, 0.05, 200])";
|
||||
if eval "!is_empty(bayes_result)" {
|
||||
if eval "bayes_result > 0.7" {
|
||||
let "t.BAYES_SPAM" "1";
|
||||
|
||||
33
resources/config/sieve/config.sieve
Normal file
33
resources/config/sieve/config.sieve
Normal file
@@ -0,0 +1,33 @@
|
||||
# Whether to add an X-Spam-Status header
|
||||
let "ADD_HEADER_SPAM" "true";
|
||||
|
||||
# Whether to add an X-Spam-Result header
|
||||
let "ADD_HEADER_SPAM_RESULT" "true";
|
||||
|
||||
# Whether message replies from authenticated users should be learned as ham
|
||||
let "AUTOLEARN_REPLIES_HAM" "true";
|
||||
|
||||
# Whether the bayes classifier should be trained automatically
|
||||
let "AUTOLEARN_ENABLE" "true";
|
||||
|
||||
# When to learn ham (score >= threshold)
|
||||
let "AUTOLEARN_HAM_THRESHOLD" "-0.5";
|
||||
|
||||
# When to learn spam (score <= threshold)
|
||||
let "AUTOLEARN_SPAM_THRESHOLD" "6.0";
|
||||
|
||||
# Keep difference for spam/ham learns for at least this value
|
||||
let "AUTOLEARN_SPAM_HAM_BALANCE" "0.9";
|
||||
|
||||
# If ADD_HEADER_SPAM is enabled, mark as SPAM messages with a score above this threshold
|
||||
let "SCORE_SPAM_THRESHOLD" "5.0";
|
||||
|
||||
# Discard messages with a score above this threshold
|
||||
let "SCORE_DISCARD_THRESHOLD" "0";
|
||||
|
||||
# Reject messages with a score above this threshold
|
||||
let "SCORE_REJECT_THRESHOLD" "0";
|
||||
|
||||
# Directory name to use for local domain lookups
|
||||
let "DOMAIN_DIRECTORY" "'default'";
|
||||
|
||||
58
resources/config/sieve/epilogue.sieve
Normal file
58
resources/config/sieve/epilogue.sieve
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
# Train the bayes classifier automatically
|
||||
if eval "AUTOLEARN_ENABLE && (score >= AUTOLEARN_SPAM_THRESHOLD || score <= AUTOLEARN_HAM_THRESHOLD)" {
|
||||
let "is_spam" "score >= AUTOLEARN_SPAM_THRESHOLD";
|
||||
eval "bayes_is_balanced('spamdb/token-lookup', is_spam, AUTOLEARN_SPAM_HAM_BALANCE) &&
|
||||
bayes_train('spamdb/token-insert', body_and_subject, is_spam)";
|
||||
}
|
||||
|
||||
add balance
|
||||
|
||||
# Iterate over tags
|
||||
let "tags" "var_names()";
|
||||
let "i" "count(tags)";
|
||||
let "spam_result" "";
|
||||
while "i > 0" {
|
||||
let "i" "i - 1";
|
||||
let "tag" "tags[i]";
|
||||
let "tag_score" "map('spam/scores', tag)";
|
||||
|
||||
if eval "is_number(tag_score)" {
|
||||
let "score" "score + tag_score";
|
||||
if eval "ADD_HEADER_SPAM_RESULT" {
|
||||
if eval "!is_empty(spam_result)" {
|
||||
let "spam_result" "spam_result + ',\r\n\t' + tag + ' (' + tag_score + ')'";
|
||||
} else {
|
||||
let "spam_result" "spam_result + tag + ' (' + tag_score + ')'";
|
||||
}
|
||||
}
|
||||
} elsif eval "tag_score == 'reject' {
|
||||
let "SCORE_REJECT_THRESHOLD" "1";
|
||||
let "score" "2";
|
||||
break;
|
||||
} else if eval "tag_score == 'discard'" {
|
||||
discard;
|
||||
stop;
|
||||
}
|
||||
}
|
||||
|
||||
# Process score actions
|
||||
if "SCORE_REJECT_THRESHOLD && score >= SCORE_REJECT_THRESHOLD" {
|
||||
reject "Your message has been rejected because it has an excessive spam score. If you feel this is an error, please contact the postmaster.";
|
||||
stop;
|
||||
} else if "SCORE_DISCARD_THRESHOLD && score >= SCORE_DISCARD_THRESHOLD" {
|
||||
discard;
|
||||
stop;
|
||||
} else if "ADD_HEADER_SPAM" {
|
||||
let "spam_status" "";
|
||||
if eval "score >= SCORE_SPAM_THRESHOLD" {
|
||||
let "spam_status" "'Yes, score=' + score";
|
||||
} else {
|
||||
let "spam_status" "'No, score=' + score";
|
||||
}
|
||||
eval "add-header('X-Spam-Status', spam_status)";
|
||||
if eval "!is_empty(spam_result)" {
|
||||
eval "add-header('X-Spam-Result', spam_result)";
|
||||
}
|
||||
}
|
||||
|
||||
9
resources/config/sieve/greylist.sieve
Normal file
9
resources/config/sieve/greylist.sieve
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
set "triplet" "${env.remote_ip}.${envelope.from}.${envelope.to}";
|
||||
|
||||
if eval "!lookup('spamdb/id-lookup', triplet)" {
|
||||
# Greylist sender for 30 days
|
||||
eval "lookup_map('spamdb/id-insert', [triplet, 2592000])";
|
||||
reject "422 4.2.2 Greylisted, please try again in a few moments.";
|
||||
stop;
|
||||
}
|
||||
@@ -9,6 +9,11 @@ if eval "!header.mime-version.exists" {
|
||||
let "has_text_part" "0";
|
||||
let "is_encrypted" "0";
|
||||
|
||||
if eval "header.Content-Type.exists && !header.Content-Disposition:Content-Transfer-Encoding:MIME-Version.exists && !eq_ignore_case(header.Content-Type, 'text/plain')" {
|
||||
# Only Content-Type header without other MIME headers
|
||||
let "t.MIME_HEADER_CTYPE_ONLY" "1";
|
||||
}
|
||||
|
||||
foreverypart {
|
||||
let "content_type" "to_lowercase(header.content-type)";
|
||||
let "type" "to_lowercase(header.content-type.type)";
|
||||
@@ -138,13 +143,8 @@ foreverypart {
|
||||
}
|
||||
}
|
||||
|
||||
if eval "is_empty(type)" {
|
||||
if eval "header.content-type.exists" {
|
||||
let "t.BROKEN_CONTENT_TYPE" "1";
|
||||
}
|
||||
} elsif eval "!header.Content-Disposition:Content-Transfer-Encoding:MIME-Version.exists && (type != 'text' || subtype != 'plain')" {
|
||||
# Only Content-Type header without other MIME headers
|
||||
let "t.MIME_HEADER_CTYPE_ONLY" "1";
|
||||
if eval "is_empty(type) && header.content-type.exists" {
|
||||
let "t.BROKEN_CONTENT_TYPE" "1";
|
||||
}
|
||||
|
||||
if eval "part_is_attachment" {
|
||||
|
||||
@@ -38,3 +38,6 @@ let "envfrom_domain_sld" "domain_part(envfrom_domain, 'sld')";
|
||||
|
||||
# Obtain HELO domain SLD
|
||||
let "helo_domain_sld" "domain_part(env.helo_domain, 'sld')";
|
||||
|
||||
# Create score variable
|
||||
let "score" "0.0";
|
||||
|
||||
@@ -171,7 +171,8 @@ while "i < domains_len" {
|
||||
let "domain" "domains[i]";
|
||||
let "i" "i + 1";
|
||||
|
||||
if eval "!contains(domain, '.') || is_ip_addr(domain)" {
|
||||
# Skip invalid and local domain names
|
||||
if eval "!contains(domain, '.') || is_ip_addr(domain) || is_local_domain(DOMAIN_DIRECTORY, domain_part(domain, 'sld'))" {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -286,7 +287,8 @@ while "i < emails_len" {
|
||||
let "email" "emails[i]";
|
||||
let "i" "i + 1";
|
||||
|
||||
if eval "!contains(email, '@')" {
|
||||
# Skip invalid and local e-mail addresses
|
||||
if eval "!contains(email, '@') || is_local_domain(DOMAIN_DIRECTORY, domain_part(email_part(email, 'domain'), 'sld'))" {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -319,6 +321,11 @@ while "i < urls_len" {
|
||||
break;
|
||||
}
|
||||
|
||||
# Skip URLs pointing to local domains
|
||||
if eval "is_local_domain(DOMAIN_DIRECTORY, domain_part(uri_part(url, 'host'), 'sld'))" {
|
||||
continue;
|
||||
}
|
||||
|
||||
# Query SURBL HASHBL
|
||||
let "result" "rsplit_once(dns_query(hash(url, 'md5') + '.hashbl.surbl.org', 'ipv4')[0], '.')";
|
||||
if eval "starts_with(result[0], '127.0.')" {
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
let "message_id" "header.Message-ID";
|
||||
|
||||
if eval "!is_empty(message_id)" {
|
||||
eval "lookup('spamdb/id-insert', message_id)";
|
||||
# Store the message ID for 30 days
|
||||
eval "lookup_map('spamdb/id-insert', [message_id, 2592000])";
|
||||
|
||||
if eval "lookup('spam/options', 'AUTOLEARN_REPLIES')" {
|
||||
if eval "AUTOLEARN_ENABLE && AUTOLEARN_REPLIES_HAM && bayes_is_balanced('spamdb/token-lookup', false, AUTOLEARN_SPAM_HAM_BALANCE)" {
|
||||
eval "bayes_train('spamdb/token-insert', thread_name(header.subject) + ' ' + body.to_text, false)";
|
||||
}
|
||||
}
|
||||
|
||||
0
resources/config/sieve/scores.sieve
Normal file
0
resources/config/sieve/scores.sieve
Normal file
@@ -1,6 +1,9 @@
|
||||
|
||||
# Check if the message was sent to a spam trap address
|
||||
if eval "lookup('spam/trap-address', envelope.to)" {
|
||||
eval "bayes_train('spamdb/token-insert', body_and_subject, true)";
|
||||
if eval "AUTOLEARN_ENABLE && lookup('spam/trap-address', envelope.to)" {
|
||||
eval "bayes_is_balanced('spamdb/token-lookup', false, AUTOLEARN_SPAM_HAM_BALANCE) && bayes_train('spamdb/token-insert', body_and_subject, true)";
|
||||
let "t.SPAM_TRAP" "1";
|
||||
|
||||
# Disable autolearn so the classifier is not trained twice
|
||||
let "AUTOLEARN_ENABLE" "0";
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ let "i" "count(urls)";
|
||||
while "i > 0" {
|
||||
let "i" "i - 1";
|
||||
let "url" "urls[i]";
|
||||
|
||||
# Skip non-URLs such as 'data:' and 'mailto:'
|
||||
if eval "!contains(url, '://')" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let "host" "uri_part(url, 'host')";
|
||||
|
||||
if eval "!is_empty(host)" {
|
||||
@@ -20,6 +26,10 @@ while "i > 0" {
|
||||
let "host_lc" "to_lowercase(host)";
|
||||
let "host_sld" "domain_part(host_lc, 'sld')";
|
||||
|
||||
if eval "is_local_domain(DOMAIN_DIRECTORY, host_sld)" {
|
||||
continue;
|
||||
}
|
||||
|
||||
if eval "!is_ip &&
|
||||
(!t.REDIRECTOR_URL || !t.URL_REDIRECTOR_NESTED) &&
|
||||
lookup('spam/redirectors', host_sld)" {
|
||||
|
||||
Reference in New Issue
Block a user