Key/value store access from Sieve scripts
This commit is contained in:
@@ -6,7 +6,7 @@ if eval "!t.SPAM_TRAP && !t.TRUSTED_REPLY" {
|
||||
# min_prob_strength: 0.05
|
||||
# min_learns: 200
|
||||
|
||||
let "bayes_result" "bayes_classify('spamdb/token-lookup', body_and_subject, [2, 11, 0.05, 200])";
|
||||
let "bayes_result" "bayes_classify(SPAM_DB, 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";
|
||||
|
||||
@@ -28,6 +28,8 @@ 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'";
|
||||
# Directory name to use for local domain lookups (leave empty for default)
|
||||
let "DOMAIN_DIRECTORY" "";
|
||||
|
||||
# Store to use for Bayes tokens and ids (leave empty for default)
|
||||
let "SPAM_DB" "";
|
||||
|
||||
@@ -64,13 +64,13 @@ if eval "header.DKIM-Signature.exists" {
|
||||
}
|
||||
|
||||
# Check allowlists
|
||||
if eval "lookup('spam/dmarc-allow', from_domain)" {
|
||||
if eval "key_exists('spam/dmarc-allow', from_domain)" {
|
||||
if eval "t.DMARC_POLICY_ALLOW" {
|
||||
let "t.ALLOWLIST_DMARC" "1";
|
||||
} else {
|
||||
let "t.BLOCKLIST_DMARC" "1";
|
||||
}
|
||||
} elsif eval "lookup('spam/spf-dkim-allow', from_domain)" {
|
||||
} elsif eval "key_exists('spam/spf-dkim-allow', from_domain)" {
|
||||
let "is_dkim_pass" "contains(env.dkim.domains, from_domain) || t.ARC_ALLOW";
|
||||
|
||||
if eval "is_dkim_pass && t.SPF_ALLOW" {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# 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)";
|
||||
eval "bayes_is_balanced(SPAM_DB, is_spam, AUTOLEARN_SPAM_HAM_BALANCE) &&
|
||||
bayes_train(SPAM_DB, body_and_subject, is_spam)";
|
||||
}
|
||||
|
||||
# Process score actions
|
||||
|
||||
@@ -16,9 +16,9 @@ if eval "from_count > 0" {
|
||||
let "t.WWW_DOT_DOMAIN" "1";
|
||||
}
|
||||
|
||||
if eval "lookup('spam/free-domains', from_domain_sld)" {
|
||||
if eval "key_exists('spam/free-domains', from_domain_sld)" {
|
||||
let "t.FREEMAIL_FROM" "1";
|
||||
} elsif eval "lookup('spam/disposable-domains', from_domain_sld)" {
|
||||
} elsif eval "key_exists('spam/disposable-domains', from_domain_sld)" {
|
||||
let "t.DISPOSABLE_FROM" "1";
|
||||
}
|
||||
} else {
|
||||
@@ -125,9 +125,9 @@ if eval "!is_empty(envelope.from)" {
|
||||
}
|
||||
|
||||
if eval "!is_empty(envfrom_domain_sld)" {
|
||||
if eval "lookup('spam/free-domains', envfrom_domain_sld)" {
|
||||
if eval "key_exists('spam/free-domains', envfrom_domain_sld)" {
|
||||
let "t.FREEMAIL_ENVFROM" "1";
|
||||
} elsif eval "lookup('spam/disposable-domains', envfrom_domain_sld)" {
|
||||
} elsif eval "key_exists('spam/disposable-domains', envfrom_domain_sld)" {
|
||||
let "t.DISPOSABLE_ENVFROM" "1";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
set "triplet" "${env.remote_ip}.${envelope.from}.${envelope.to}";
|
||||
set "triplet" "g:${env.remote_ip}.${envelope.from}.${envelope.to}";
|
||||
|
||||
if eval "!lookup('spamdb/id-lookup', triplet)" {
|
||||
if eval "!key_exists(SPAMDB, triplet)" {
|
||||
# Greylist sender for 30 days
|
||||
eval "lookup_map('spamdb/id-insert', [triplet, 2592000])";
|
||||
eval "key_set(SPAMDB, triplet, '', 2592000)";
|
||||
reject "422 4.2.2 Greylisted, please try again in a few moments.";
|
||||
stop;
|
||||
}
|
||||
|
||||
@@ -186,9 +186,9 @@ foreverypart {
|
||||
}
|
||||
let "name_parts" "rsplit(to_lowercase(attach_name), '.')";
|
||||
if eval "count(name_parts) > 1" {
|
||||
let "ext_type" "lookup_map('spam/mime-types', name_parts[0])";
|
||||
let "ext_type" "key_get('spam/mime-types', name_parts[0])";
|
||||
if eval "!is_empty(ext_type)" {
|
||||
let "ext_type_double" "lookup_map('spam/mime-types', name_parts[1])";
|
||||
let "ext_type_double" "key_get('spam/mime-types', name_parts[1])";
|
||||
if eval "contains(ext_type, 'BAD')" {
|
||||
# Bad extension
|
||||
if eval "contains(ext_type_double, 'BAD')" {
|
||||
|
||||
@@ -175,7 +175,7 @@ while "i < domains_len" {
|
||||
if eval "!contains(domain, '.') ||
|
||||
is_ip_addr(domain) ||
|
||||
is_local_domain(DOMAIN_DIRECTORY, domain_part(domain, 'sld')) ||
|
||||
lookup('spam/domains-allow', domain)" {
|
||||
key_exists('spam/domains-allow', domain)" {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ while "i < urls_len" {
|
||||
# Skip URLs pointing to local or trusted domains
|
||||
let "domain" "domain_part(uri_part(url, 'host'), 'sld')";
|
||||
if eval "is_local_domain(DOMAIN_DIRECTORY, domain) ||
|
||||
lookup('spam/domains-allow', domain)" {
|
||||
key_exists('spam/domains-allow', domain)" {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,13 +89,13 @@ if eval "rcpt_count > 0" {
|
||||
# Check for freemail or disposable domains
|
||||
let "domain" "domain_part(email_part(addr, 'domain'), 'sld')";
|
||||
if eval "!is_empty(domain)" {
|
||||
if eval "lookup('spam/free-domains', domain)" {
|
||||
if eval "key_exists('spam/free-domains', 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 "lookup('spam/disposable-domains', domain)" {
|
||||
} elsif eval "key_exists('spam/disposable-domains', 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)" {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
|
||||
if eval "lookup('spamdb/id-lookup', header.In-Reply-To:References)" {
|
||||
let "t.TRUSTED_REPLY" "1";
|
||||
let "message_ids" "header.In-Reply-To:References";
|
||||
|
||||
let "i" "count(message_ids)";
|
||||
while "i > 0" {
|
||||
let "i" "i - 1";
|
||||
|
||||
if eval "key_exists(SPAM_DB, 'm:' + message_ids[i])" {
|
||||
let "t.TRUSTED_REPLY" "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ let "message_id" "header.Message-ID";
|
||||
|
||||
if eval "!is_empty(message_id)" {
|
||||
# Store the message ID for 30 days
|
||||
eval "lookup_map('spamdb/id-insert', [message_id, 2592000])";
|
||||
eval "key_set(SPAM_DB, 'm:' + message_id, '', 2592000)";
|
||||
|
||||
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)";
|
||||
if eval "AUTOLEARN_ENABLE && AUTOLEARN_REPLIES_HAM && bayes_is_balanced(SPAM_DB, false, AUTOLEARN_SPAM_HAM_BALANCE)" {
|
||||
eval "bayes_train(SPAM_DB, thread_name(header.subject) + ' ' + body.to_text, false)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,12 +49,12 @@ if eval "!is_empty(rto_raw)" {
|
||||
let "t.REPLYTO_ADDR_EQ_FROM" "1";
|
||||
}
|
||||
|
||||
if eval "lookup('spam/free-domains', rto_domain_sld)" {
|
||||
if eval "key_exists('spam/free-domains', rto_domain_sld)" {
|
||||
let "t.FREEMAIL_REPLYTO" "1";
|
||||
if eval "rto_domain_sld != from_domain_sld && lookup('spam/free-domains', from_domain_sld)" {
|
||||
if eval "rto_domain_sld != from_domain_sld && key_exists('spam/free-domains', from_domain_sld)" {
|
||||
let "t.FREEMAIL_REPLYTO_NEQ_FROM_DOM" "1";
|
||||
}
|
||||
} elsif eval "lookup('spam/disposable-domains', rto_domain_sld)" {
|
||||
} elsif eval "key_exists('spam/disposable-domains', rto_domain_sld)" {
|
||||
let "t.DISPOSABLE_REPLYTO" "1";
|
||||
}
|
||||
|
||||
|
||||
@@ -37,15 +37,20 @@ while "i > 0" {
|
||||
let "token_id" "token_ids[i]";
|
||||
|
||||
# Lookup reputation
|
||||
let "token_rep" "lookup_map('spamdb/reputation-lookup', token_id)";
|
||||
|
||||
# Update reputation
|
||||
eval "lookup_map('spamdb/reputation-insert', [token_id, score])";
|
||||
let "token_rep" "key_get(SPAM_DB, token_id)";
|
||||
|
||||
if eval "is_empty(token_rep)" {
|
||||
# Set reputation
|
||||
eval "key_set(SPAM_DB, token_id, [score, 1], 2592000)";
|
||||
continue;
|
||||
}
|
||||
|
||||
# Update reputation
|
||||
let "token_score" "token_rep[0]";
|
||||
let "token_count" "token_rep[1]";
|
||||
let "updated_score" "(token_count + 1) * (score + 0.98 * token_score) / (0.98 * token_count + 1)";
|
||||
eval "key_set(SPAM_DB, token_id, [updated_score, token_count + 1], 2592000)";
|
||||
|
||||
# Assign weight
|
||||
let "weight" "";
|
||||
if eval "starts_with(token_id, 'f:')" {
|
||||
@@ -64,7 +69,7 @@ while "i > 0" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let "reputation" "reputation + (token_rep[0] / token_rep[1] * weight)";
|
||||
let "reputation" "reputation + (token_score / token_count * weight)";
|
||||
}
|
||||
|
||||
# Adjust score using a 0.5 factor
|
||||
|
||||
@@ -5,7 +5,7 @@ let "spam_result" "";
|
||||
while "i > 0" {
|
||||
let "i" "i - 1";
|
||||
let "tag" "tags[i]";
|
||||
let "tag_score" "lookup_map('spam/scores', tag)";
|
||||
let "tag_score" "key_get('spam/scores', tag)";
|
||||
|
||||
if eval "is_number(tag_score)" {
|
||||
let "score" "score + tag_score";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
# Check if the message was sent to a spam trap address
|
||||
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)";
|
||||
if eval "AUTOLEARN_ENABLE && key_exists('spam/trap-address', envelope.to)" {
|
||||
eval "bayes_is_balanced(SPAM_DB, false, AUTOLEARN_SPAM_HAM_BALANCE) && bayes_train(SPAM_DB, body_and_subject, true)";
|
||||
let "t.SPAM_TRAP" "1";
|
||||
|
||||
# Disable autolearn so the classifier is not trained twice
|
||||
|
||||
@@ -27,13 +27,13 @@ while "i > 0" {
|
||||
let "host_sld" "domain_part(host_lc, 'sld')";
|
||||
|
||||
# Skip local and trusted domains
|
||||
if eval "is_local_domain(DOMAIN_DIRECTORY, host_sld) || lookup('spam/domains-allow', host_sld)" {
|
||||
if eval "is_local_domain(DOMAIN_DIRECTORY, host_sld) || key_exists('spam/domains-allow', host_sld)" {
|
||||
continue;
|
||||
}
|
||||
|
||||
if eval "!is_ip &&
|
||||
(!t.REDIRECTOR_URL || !t.URL_REDIRECTOR_NESTED) &&
|
||||
lookup('spam/redirectors', host_sld)" {
|
||||
key_exists('spam/redirectors', host_sld)" {
|
||||
let "t.REDIRECTOR_URL" "1";
|
||||
let "redir_count" "1";
|
||||
|
||||
@@ -48,7 +48,7 @@ while "i > 0" {
|
||||
let "host_lc" "to_lowercase(host)";
|
||||
let "host_sld" "domain_part(host_lc, 'sld')";
|
||||
|
||||
if eval "!is_ip && lookup('spam/redirectors', host_sld)" {
|
||||
if eval "!is_ip && key_exists('spam/redirectors', host_sld)" {
|
||||
let "redir_count" "redir_count + 1";
|
||||
} else {
|
||||
break;
|
||||
@@ -110,10 +110,10 @@ while "i > 0" {
|
||||
}
|
||||
|
||||
# Phishing checks (refresh OpenPhish every 12 hours, PhishTank every 6 hours)
|
||||
if eval "lookup_remote('https://openphish.com/feed.txt', url, [43200, 'list'])" {
|
||||
if eval "key_exists_http('https://openphish.com/feed.txt', url, [43200, 'list'])" {
|
||||
let "t.PHISHED_OPENPHISH" "1";
|
||||
}
|
||||
if eval "lookup_remote('http://data.phishtank.com/data/online-valid.csv', url, [21600, 'csv', 1, ',', true])" {
|
||||
if eval "key_exists_http('http://data.phishtank.com/data/online-valid.csv', url, [21600, 'csv', 1, ',', true])" {
|
||||
let "t.PHISHED_PHISHTANK" "1";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user