Key/value store access from Sieve scripts

This commit is contained in:
mdecimus
2023-12-12 15:28:21 +01:00
parent 78f932990d
commit 4301ec16d0
46 changed files with 1087 additions and 592 deletions

View File

@@ -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";

View File

@@ -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" "";

View File

@@ -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" {

View File

@@ -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

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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')" {

View File

@@ -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;
}

View File

@@ -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)" {

View File

@@ -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;
}
}

View File

@@ -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)";
}
}

View File

@@ -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";
}

View File

@@ -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

View File

@@ -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";

View File

@@ -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

View File

@@ -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";
}