Sieve antispam functions, bump OpenTelemetry version
This commit is contained in:
@@ -4,7 +4,7 @@ require [ "variables",
|
||||
"regex",
|
||||
"body",
|
||||
"reject",
|
||||
"vnd.stalwart.foreveryline",
|
||||
"vnd.stalwart.while",
|
||||
"vnd.stalwart.eval",
|
||||
"vnd.stalwart.plugins"];
|
||||
|
||||
@@ -12,16 +12,6 @@ set "body" "%{body.to_text}";
|
||||
set "body_len" "%{len(body)}";
|
||||
set "headers_raw" "%{header.*.raw}";
|
||||
set "headers_text" "%{header.*.text}";
|
||||
#set "thread_name" "%{header.subject.thread_name()}";
|
||||
#set "sent_date" "%{header.date.date}";
|
||||
#set "mail_from" "%{envelope.from}";
|
||||
#if eval "mail_from.is_empty()" {
|
||||
# set "mail_from" "postmaster@${env.helo_domain}";
|
||||
#}
|
||||
#set "mail_from_domain" "%{mail_from.domain_part()}";
|
||||
#set "from" "%{header.from.addr}";
|
||||
#set "from_domain" "%{from.domain_part()}";
|
||||
#set "from_name" "%{header.from.name.trim()}";
|
||||
|
||||
# Message only has text/html MIME parts
|
||||
if eval "header.content-type == 'text/html'" {
|
||||
@@ -29,10 +19,20 @@ if eval "header.content-type == 'text/html'" {
|
||||
set "t.__MIME_HTML" "1";
|
||||
}
|
||||
|
||||
# Part count
|
||||
set "mime_text_html_count" "0";
|
||||
set "mime_text_plain_count" "0";
|
||||
set "mime_part_count" "0";
|
||||
|
||||
# Inline image count and area
|
||||
set "mime_gif_count" "0";
|
||||
set "mime_png_count" "0";
|
||||
set "mime_jpg_count" "0";
|
||||
set "mime_img_count" "0";
|
||||
set "mime_img_area" "0";
|
||||
set "mime_gif_area" "0";
|
||||
set "mime_png_area" "0";
|
||||
|
||||
foreverypart {
|
||||
set "ct" "%{to_lowercase(header.content-type)}";
|
||||
|
||||
@@ -41,7 +41,190 @@ foreverypart {
|
||||
set "mime_text_plain_count" "%{mime_text_plain_count + 1}";
|
||||
} elsif eval "ct == 'text/html'" {
|
||||
set "mime_text_html_count" "%{mime_text_html_count + 1}";
|
||||
}
|
||||
set "t.HTML_MESSAGE" "1";
|
||||
|
||||
# Tokenize HTML
|
||||
set "html_tokens" "${tokenize_html(part.text)}";
|
||||
set "html_char_count" "0";
|
||||
set "html_space_count" "0";
|
||||
set "html_img_area" "0";
|
||||
set "in_head" "0";
|
||||
set "in_body" "0";
|
||||
set "in_center" "0";
|
||||
set "in_title" "0";
|
||||
set "in_anchor" "0";
|
||||
set "in_anchor_href" "";
|
||||
|
||||
set "i" "%{len(html_tokens)}";
|
||||
while "i" {
|
||||
set "i" "%{i - 1}";
|
||||
set "line" "%{html_tokens[i]}";
|
||||
|
||||
# Tokens starting with '_' are text nodes
|
||||
if eval "starts_with(line, '_')" {
|
||||
if eval "in_head == 0" {
|
||||
set "html_char_count" "%{html_char_count + count_chars(line) - 1}";
|
||||
set "html_space_count" "%{html_space_count + count_spaces(line)}";
|
||||
}
|
||||
set "text_len" "%{len(line) - 1}";
|
||||
if eval "in_title && !is_empty(header.subject) && text_len / len(header.subject) > 3.5" {
|
||||
set "t.__HTML_TITLE_SUBJ_DIFF" "1";
|
||||
}
|
||||
if eval "text_len == 120" {
|
||||
set "t.__HTML_TITLE_120" "1";
|
||||
}
|
||||
if eval "in_anchor &&
|
||||
!is_empty(in_anchor_href) &&
|
||||
contains_ignore_case(line, 'http') &&
|
||||
uri_part(trim(substring(line, 1, text_len)), 'scheme_host') != in_anchor_href" {
|
||||
set "t.HTTPS_HTTP_MISMATCH" "1";
|
||||
}
|
||||
} elsif eval "starts_with(line, '<!--')" {
|
||||
set "t.__COMMENT_EXISTS" "1";
|
||||
if eval "len(line) - 4 <= 6" {
|
||||
set "t.HTML_COMMENT_SHORT" "1";
|
||||
} elsif eval "starts_with(line, '<!-- saved from url=')" {
|
||||
set "t.HTML_COMMENT_SAVED_URL" "1";
|
||||
}
|
||||
} elsif eval "starts_with(line, '<img')" {
|
||||
set "width" "%{html_attr_int(line, 'width', 800)}";
|
||||
set "height" "%{html_attr_int(line, 'height', 600)}";
|
||||
|
||||
if "!width" {
|
||||
set "width" "200";
|
||||
}
|
||||
if "!height" {
|
||||
set "height" "200";
|
||||
}
|
||||
|
||||
set "html_img_area" "%{html_img_area + (width * height)}";
|
||||
|
||||
if eval "in_anchor" {
|
||||
set "t.__HTML_LINK_IMAGE" "1";
|
||||
}
|
||||
|
||||
} elsif eval "starts_with(line, '<bgsound')" {
|
||||
set "t.HTML_TAG_EXIST_BGSOUND" "1";
|
||||
} elsif eval "starts_with(line, '<center')" {
|
||||
set "t.__TAG_EXISTS_CENTER" "1";
|
||||
set "in_center" "${in_center + 1}";
|
||||
} elsif eval "starts_with(line, '</center')" {
|
||||
set "in_center" "${in_center - 1}";
|
||||
} elsif eval "starts_with(line, '<body')" {
|
||||
set "t.__TAG_EXISTS_BODY" "1";
|
||||
set "in_body" "${in_body + 1}";
|
||||
} elsif eval "starts_with(line, '</body')" {
|
||||
set "in_body" "${in_body - 1}";
|
||||
} elsif eval "starts_with(line, '<head')" {
|
||||
set "t.__TAG_EXISTS_HEAD" "1";
|
||||
set "in_head" "${in_head + 1}";
|
||||
} elsif eval "starts_with(line, '</head')" {
|
||||
set "in_head" "%{in_head - 1}";
|
||||
} elsif eval "starts_with(line, '<title')" {
|
||||
set "in_title" "1";
|
||||
} elsif eval "in_title && starts_with(line, '</title')" {
|
||||
set "in_title" "0";
|
||||
} elsif eval "starts_with(line, '<a ')" {
|
||||
set "in_anchor" "1";
|
||||
set "in_anchor_href" "%{uri_part(trim(html_attr(line, 'href')), 'scheme_host')}";
|
||||
} elsif eval "in_anchor && starts_with(line, '</a')" {
|
||||
set "in_anchor" "0";
|
||||
} elsif eval "starts_with(line, '<html')" {
|
||||
set "t.__TAG_EXISTS_HTML" "1";
|
||||
} elsif eval "starts_with(line, '<meta')" {
|
||||
set "t.__TAG_EXISTS_META" "1";
|
||||
} elsif eval "starts_with(line, '<style')" {
|
||||
set "t.__TAG_EXISTS_STYLE" "1";
|
||||
} elsif eval "starts_with(line, '<script')" {
|
||||
set "t.__TAG_EXISTS_SCRIPT" "1";
|
||||
} elsif eval "starts_with(line, '<iframe')" {
|
||||
if eval "!is_empty(html_attr(line, 'src'))" {
|
||||
set "t.HTML_IFRAME_SRC" "1";
|
||||
}
|
||||
} elsif eval "starts_with(line, '<embed') || starts_with(line, '<object')" {
|
||||
set "t.HTML_EMBEDS" "1";
|
||||
} elsif eval "starts_with(line, '<form')" {
|
||||
if eval "starts_with(html_attr(line, 'action'), 'mailto')" {
|
||||
set "t.HTML_FORMACTION_MAILTO" "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if eval "in_head != 0" {
|
||||
set "t.HTML_TAG_BALANCE_HEAD" "1";
|
||||
}
|
||||
|
||||
if eval "in_body != 0" {
|
||||
set "t.HTML_TAG_BALANCE_BODY" "1";
|
||||
}
|
||||
|
||||
if eval "in_center != 0" {
|
||||
set "t.__HTML_TAG_BALANCE_CENTER" "1";
|
||||
}
|
||||
|
||||
if eval "html_img_area > 0" {
|
||||
# Calculate image to word ratio
|
||||
set "html_img_ratio" "%{(html_char_count - html_space_count) / html_img_area}";
|
||||
if eval "html_img_ratio <= 0.002" {
|
||||
set "t.HTML_IMAGE_RATIO_02" "1";
|
||||
} elsif html_img_ratio <= 0.004" {
|
||||
set "t.HTML_IMAGE_RATIO_04" "1";
|
||||
} elsif eval "html_img_ratio <= 0.006" {
|
||||
set "t.HTML_IMAGE_RATIO_06" "1";
|
||||
} elsif eval "html_img_ratio <= '0.008" {
|
||||
set "t.HTML_IMAGE_RATIO_08" "1";
|
||||
}
|
||||
|
||||
# Check for not much raw HTML with images
|
||||
if eval "html_char_count <= 400" {
|
||||
set "t.HTML_IMAGE_ONLY_04" "1";
|
||||
} elsif eval "html_char_count <= 800" {
|
||||
set "t.HTML_IMAGE_ONLY_08" "1";
|
||||
} elsif eval "html_char_count <= 1200 " {
|
||||
set "t.HTML_IMAGE_ONLY_12" "1";
|
||||
} elsif eval "html_char_count <= 1600" {
|
||||
set "t.HTML_IMAGE_ONLY_16" "1";
|
||||
} elsif eval "html_char_count <= 2000 " {
|
||||
set "t.HTML_IMAGE_ONLY_20" "1";
|
||||
} elsif eval "html_char_count <= 2400" {
|
||||
set "t.HTML_IMAGE_ONLY_24" "1";
|
||||
} elsif eval "html_char_count <= 2800 " {
|
||||
set "t.HTML_IMAGE_ONLY_28" "1";
|
||||
} elsif eval "html_char_count <= 3200 " {
|
||||
set "t.HTML_IMAGE_ONLY_32" "1";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "html_char_count <= 384" {
|
||||
set "t.__HTML_LENGTH_384" "1";
|
||||
} elsif eval "html_char_count <= 512" {
|
||||
set "t.__HTML_LENGTH_512" "1";
|
||||
} elsif eval "html_char_count <= 1024" {
|
||||
set "t.__HTML_LENGTH_0000_1024" "1";
|
||||
} elsif eval "html_char_count <= 1536" {
|
||||
set "t.__HTML_LENGTH_1024_1536" "1";
|
||||
} elsif eval "html_char_count <= 2048 " {
|
||||
set "t.__HTML_LENGTH_1536_2048" "1";
|
||||
}
|
||||
} elsif eval "eq_ignore_case(header.content-type.type, 'image')" {
|
||||
# Obtain image type and area
|
||||
set "img_area" "%{img_metadata('area')}";
|
||||
set "img_type" "%{img_metadata('type')}";
|
||||
|
||||
if eval "img_type == 'gif'" {
|
||||
set "mime_gif_count" "%{mime_gif_count + 1}";
|
||||
set "mime_img_area" "%{mime_img_area + img_area}";
|
||||
} elsif eval "img_type =='png'" {
|
||||
set "mime_png_count" "%{mime_png_count + 1}";
|
||||
set "mime_png_area" "%{mime_png_area + img_area}";
|
||||
} elsif eval "img_type == 'jpeg'" {
|
||||
set "mime_jpg_count" "%{mime_jpg_count + 1}";
|
||||
} else {
|
||||
set "mime_img_count" "%{mime_img_count + 1}";
|
||||
}
|
||||
|
||||
set "mime_img_area" "%{mime_img_area + img_area}";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "is_attachment()" {
|
||||
@@ -145,20 +328,66 @@ foreverypart {
|
||||
}
|
||||
}
|
||||
|
||||
# Message has at least one text/html MIME part
|
||||
if eval "mime_text_html_count > 0 && mime_text_plain_count == 0" {
|
||||
set "t.__MIME_HTML" "1";
|
||||
}
|
||||
|
||||
set "vertical_lines" "0";
|
||||
set "total_lines" "0";
|
||||
# Image counts
|
||||
if eval "mime_gif_count == 1" {
|
||||
set "t.__GIF_ATTACH_1" "1";
|
||||
} elsif eval "mime_gif_count >= 2 {
|
||||
set "t.__GIF_ATTACH_2P" "1";
|
||||
}
|
||||
if eval "mime_png_count == 1" {
|
||||
set "t.__PNG_ATTACH_1" "1";
|
||||
} elsif eval "mime_png_count >= 2 {
|
||||
set "t.__PNG_ATTACH_2P" "1";
|
||||
}
|
||||
if eval "mime_jpg_count == 1" {
|
||||
set "t.__JPEG_ATTACH_1" "1";
|
||||
} elsif eval "mime_jpg_count >= 2 {
|
||||
set "t.__JPEG_ATTACH_2P" "1";
|
||||
}
|
||||
if eval "mime_gif_count + mime_png_count + mime_jpg_count + mime_img_count == 1" {
|
||||
set "t.__ONE_IMG" "1";
|
||||
}
|
||||
|
||||
# Image to text ratios
|
||||
if eval "mime_img_area > 0" {
|
||||
if eval "len(body.html) / mime_img_area <= 0.015" {
|
||||
# Low rawbody to pixel area ratio
|
||||
set "t.__DC_IMG_HTML_RATIO" "1";
|
||||
}
|
||||
if eval "body_len / mime_img_area <= 0.008" {
|
||||
# Low body to pixel area ratio
|
||||
set "t.__DC_IMG_TEXT_RATIO" "1";
|
||||
}
|
||||
}
|
||||
if eval "mime_gif_area >= 180000 && mime_gif_area <= 475000" {
|
||||
set "t.__GIF_AREA_180K" "1";
|
||||
}
|
||||
if eval "mime_png_area >= 180000 && mime_png_area <= 475000" {
|
||||
set "t.__PNG_AREA_180K" "1";
|
||||
}
|
||||
if eval "mime_img_area >= 62500 && mime_img_area <= 300000" {
|
||||
set "t.__IMG_LE_300K" "1";
|
||||
}
|
||||
|
||||
# Vertical words
|
||||
set "body_lines" "%{lines(body)}";
|
||||
set "vertical_lines" "0";
|
||||
set "total_lines" "len(body_lines)";
|
||||
|
||||
set "i" "0";
|
||||
while "i < total_lines" {
|
||||
set "line" "%{body_lines[i]}";
|
||||
set "i" "%{i + 1}";
|
||||
|
||||
foreveryline "${body}" {
|
||||
# Vertical words in body
|
||||
if eval "len(trim(line)) == 1 || (len(line) > 5 && count_spaces(line) / count_chars(line) > 0.8)" {
|
||||
set "vertical_lines" "%{vertical_lines + 1}";
|
||||
}
|
||||
|
||||
set "total_lines" "%{line_num}";
|
||||
}
|
||||
|
||||
if eval "total_lines > 5 && vertical_lines > total_lines / 2" {
|
||||
@@ -184,13 +413,313 @@ if eval "body_len > 200" {
|
||||
if eval "!is_empty(env.iprev_ptr) && !eq_ignore_case(env.helo_domain, env.iprev_ptr)" {
|
||||
set "t.__FORGED_RCVD_TRAIL" "1";
|
||||
} else {
|
||||
foreveryline "${header.received[*].rcvd.iprev}" {
|
||||
if eval "!is_empty(line)" {
|
||||
set "helo_domain" "%{received_part(line_num, 'from')}";
|
||||
if eval "!is_empty(helo_domain) && !eq_ignore_case(helo_domain, line)" {
|
||||
set "headers" "header.received[*].rcvd.iprev";
|
||||
set "headers_len" "%{len(headers)}";
|
||||
set "i" "0";
|
||||
while "i < headers_len" {
|
||||
set "iprev" "%{headers[i]}";
|
||||
set "i" "%{i + 1}";
|
||||
|
||||
if eval "!is_empty(iprev)" {
|
||||
set "helo_domain" "%{received_part(i, 'from')}";
|
||||
if eval "!is_empty(helo_domain) && !eq_ignore_case(helo_domain, iprev)" {
|
||||
set "t.__FORGED_RCVD_TRAIL" "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check for invalid UTF-8 in headers
|
||||
if eval "!is_header_utf8_valid('Subject')" {
|
||||
set "t.__SUBJ_ILLEGAL_CHARS" "1";
|
||||
}
|
||||
if eval "!is_header_utf8_valid('From')" {
|
||||
set "t.FROM_ILLEGAL_CHARS" "1";
|
||||
}
|
||||
if eval "!is_header_utf8_valid('')" {
|
||||
set "t.__HEAD_ILLEGAL_CHARS" "1";
|
||||
}
|
||||
|
||||
# Multiple headers
|
||||
if eval "count(header.subject[*].raw) >= 2" {
|
||||
print "head %{header.subject[*].raw}";
|
||||
set "t.HEADER_COUNT_SUBJECT" "1";
|
||||
}
|
||||
if eval "count(header.content-type[*].raw) >= 2" {
|
||||
set "t.HEADER_COUNT_CTYPE" "1";
|
||||
}
|
||||
|
||||
# From name checks
|
||||
set "mail_from" "%{envelope.from}";
|
||||
if eval "is_empty(mail_from)" {
|
||||
set "mail_from" "postmaster@${env.helo_domain}";
|
||||
}
|
||||
set "mail_from_domain" "%{email_part(mail_from, 'domain')}";
|
||||
set "from" "%{to_lowercase(header.from.addr)}";
|
||||
set "from_domain" "%{email_part(from, 'domain')}";
|
||||
set "from_name" "%{to_lowercase(trim(header.from.name))}";
|
||||
|
||||
# Envelope from and From: domain match
|
||||
if eval "eq_ignore_case(from, mail_from)" {
|
||||
set "t.__ENV_AND_HDR_FROM_MATCH" "1";
|
||||
}
|
||||
|
||||
# From and EnvelopeFrom 2nd level mail domains are different
|
||||
if eval "email_part(mail_from, 'subdomain') != email_part(from, 'subdomain')" {
|
||||
set "t.HEADER_FROM_DIFFERENT_DOMAINS" "1";
|
||||
}
|
||||
|
||||
if eval "is_email(from_name)" {
|
||||
# From:name looks like a spoofed email
|
||||
if eval "domain_name_part(from_name) != domain_name_part(from)" {
|
||||
set "t.__PLUGIN_FROMNAME_SPOOF" "1";
|
||||
}
|
||||
|
||||
# From name is present in Reply-To
|
||||
if eval "contains_ignore_case(header.reply-to[*].addr[*], from_name)" {
|
||||
set "t.__FROM_EQ_REPLY" "1";
|
||||
}
|
||||
}
|
||||
|
||||
set "to_addr" "${header.to[*].addr[*]}";
|
||||
set "i" "%{len(to_addr)}";
|
||||
while "i" {
|
||||
set "i" "%{i - 1}";
|
||||
set "line" "%{to_addr[i]}";
|
||||
|
||||
if eval "!is_empty(line)" {
|
||||
# Local part of To: address appears in Subject
|
||||
if eval "contains_ignore_case(header.subject, email_part(line, 'local'))" {
|
||||
set "t.LOCALPART_IN_SUBJECT" "1";
|
||||
}
|
||||
|
||||
# From name is present in To
|
||||
if eval "eq_ignore_case(line, from_name)" {
|
||||
set "t.__PLUGIN_FROMNAME_EQUALS_TO" "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Envelope sender domain matches handover relay domain
|
||||
if eval "ends-with(env.helo_domain, mail_from_domain)" {
|
||||
set "t.__RP_MATCHES_RCVD" "1";
|
||||
}
|
||||
|
||||
# Headers contain an unresolved template
|
||||
set "i" "%{len(headers_raw)}";
|
||||
while "i" {
|
||||
set "i" "%{i - 1}";
|
||||
set "line" "%{headers_raw[i]}";
|
||||
|
||||
if allof(string :regex "line == '%[A-Z][A-Z_-]", not string :regex "line == '(?i)^(?:X-VMS-To|X-UIDL|X-Face|To|Cc|From|Subject|References|In-Reply-To|(?:X-|Resent-|X-Original-)?Message-Id):") {
|
||||
set "t.UNRESOLVED_TEMPLATE" "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
# ID contains From address
|
||||
if header :contains ["Message-Id","Resent-Message-Id","X-Message-Id","X-Original-Message-ID"] "${from}" {
|
||||
set "t.__RATWARE_NAME_ID" "1";
|
||||
}
|
||||
|
||||
# Bulk email fingerprint
|
||||
set "env_from_local" "%{email_part(mail_from, 'local')}";
|
||||
set "to_local" "%{email_part(header.to.addr, 'local')}";
|
||||
set "to_domain" "%{email_part(header.to.addr, 'domain')}";
|
||||
if eval "!is_empty(env_from_local) && !is_empty(to_local) && !is_empty(to_domain) && contains(env_from_local, to_domain) && contains(env_from_local, to_local)" {
|
||||
set "t.RATWARE_EFROM" "1";
|
||||
}
|
||||
|
||||
# Date checks
|
||||
set "sent_date" "%{header.date.date}";
|
||||
if eval "sent_date > 0" {
|
||||
set "date_diff" "%{sent_date - env.now}";
|
||||
if eval "date_diff >= -21600 && date_diff < -10800" {
|
||||
# Date: is 3 to 6 hours before Received: date
|
||||
set "t.DATE_IN_PAST_03_06" "1";
|
||||
} elsif eval "date_diff >= -43200 && date_diff < -21600" {
|
||||
# Date: is 6 to 12 hours before Received: date
|
||||
set "t.DATE_IN_PAST_06_12" "1";
|
||||
} elsif eval "date_diff >= -86400 && date_diff < -43200" {
|
||||
# Date: is 12 to 24 hours before Received: date
|
||||
set "t.DATE_IN_PAST_12_24" "1";
|
||||
} elsif eval "date_diff >= -172800 && date_diff < -86400" {
|
||||
# Date: is 24 to 48 hours before Received: date
|
||||
set "t.DATE_IN_PAST_24_48" "1";
|
||||
} elsif eval "date_diff < -345600" {
|
||||
# Date: is 96 hours or more before Received: date
|
||||
set "t.DATE_IN_PAST_96_XX" "1";
|
||||
} elsif eval "date_diff >= 10800 && date_diff < 21600" {
|
||||
# Date: is 3 to 6 hours after Received: date
|
||||
set "t.DATE_IN_FUTURE_03_06" "1";
|
||||
} elsif eval "date_diff >= 21600 && date_diff < 43200" {
|
||||
# Date: is 6 to 12 hours after Received: date
|
||||
set "t.DATE_IN_FUTURE_06_12" "1";
|
||||
} elsif eval "date_diff >= 43200 && date_diff < 86400" {
|
||||
# Date: is 12 to 24 hours after Received: date
|
||||
set "t.DATE_IN_FUTURE_12_24" "1";
|
||||
} elsif eval "date_diff >= 86400 && date_diff < 172800" {
|
||||
# Date: is 24 to 48 hours after Received: date
|
||||
set "t.DATE_IN_FUTURE_24_48" "1";
|
||||
} elsif eval "date_diff >= 172800 && date_diff < 345600" {
|
||||
# Date: is 48 to 96 hours after Received: date
|
||||
set "t.DATE_IN_FUTURE_48_96" "1";
|
||||
} elsif eval "date_diff >= 10512000" {
|
||||
# Date: is over 4 months after Received: date
|
||||
set "t.DATE_IN_FUTURE_Q_PLUS" "1";
|
||||
}
|
||||
}
|
||||
|
||||
# Subject is all capitals
|
||||
set "thread_name" "%{thread_name(header.subject)}";
|
||||
if eval "len(thread_name) >= 10 && count(tokenize_words(thread_name)) > 1 && is_uppercase(thread_name)" {
|
||||
set "t.SUBJ_ALL_CAPS" "1";
|
||||
}
|
||||
|
||||
# Received: HELO and IP do not match, but should
|
||||
if eval "!is_empty(env.iprev_ptr) && env.iprev_ptr != env.helo_domain" {
|
||||
set "t.RCVD_HELO_IP_MISMATCH" "1";
|
||||
}
|
||||
|
||||
# Host has no rDNS
|
||||
if not string :is "${env.iprev_result}" ["pass", "", "temperror"] {
|
||||
set "t.__RDNS_NONE" "1";
|
||||
}
|
||||
|
||||
# The length of the body of the email is less than 128 bytes.
|
||||
if eval "body_len < 128" {
|
||||
set "t.__KAM_BODY_LENGTH_LT_128" "1";
|
||||
}
|
||||
# The length of the body of the email is less than 512 bytes.
|
||||
if eval "body_len < 512" {
|
||||
set "t.__KAM_BODY_LENGTH_LT_512" "1";
|
||||
}
|
||||
# The length of the body of the email is less than 1024 bytes.
|
||||
if eval "body_len < 1024" {
|
||||
set "t.__KAM_BODY_LENGTH_LT_1024" "1";
|
||||
}
|
||||
|
||||
# Missing To: header
|
||||
if eval "is_empty(header.to)" {
|
||||
set "t.MISSING_HEADERS" "1";
|
||||
}
|
||||
|
||||
# DKIM checks
|
||||
set "t.__DKIM_DEPENDABLE" "1";
|
||||
if "!is_empty(env.dkim_result) && env.dkim_result != 'none'" {
|
||||
# Message has a DKIM signature, not necessarily valid
|
||||
set "t.DKIM_SIGNED" "1";
|
||||
|
||||
# Message has a valid DKIM signature from envelope-from domain
|
||||
if eval "env.dkim_result == 'pass' && mail_from == from" {
|
||||
set "t.DKIM_VALID_EF" "1";
|
||||
}
|
||||
}
|
||||
|
||||
# SPF checks
|
||||
if eval "env.spf_result == 'pass'" {
|
||||
# SPF: sender matches SPF record
|
||||
set "t.SPF_PASS" "1";
|
||||
} elsif eval "env.spf_result == 'none'" {
|
||||
# SPF: sender does not publish an SPF Record
|
||||
set "t.SPF_NONE" "1";
|
||||
} elsif eval "env.spf_result == 'neutral'" {
|
||||
# SPF: sender does not match SPF record (neutral)
|
||||
set "t.SPF_NEUTRAL" "1";
|
||||
} elsif eval "env.spf_result == 'softfail'" {
|
||||
# SPF: sender does not match SPF record (softfail)
|
||||
set "t.SPF_SOFTFAIL" "1";
|
||||
} elsif eval "env.spf_result == 'fail'" {
|
||||
# SPF: sender does not match SPF record (fail)
|
||||
set "t.SPF_FAIL" "1";
|
||||
}
|
||||
|
||||
if eval "env.spf_ehlo_result == 'pass'" {
|
||||
# SPF: HELO matches SPF record
|
||||
set "t.SPF_HELO_PASS" "1";
|
||||
} elsif eval "env.spf_ehlo_result == 'fail'" {
|
||||
# SPF: HELO does not match SPF record (fail)
|
||||
set "t.SPF_HELO_FAIL" "1";
|
||||
} elsif eval "env.spf_ehlo_result == 'none'" {
|
||||
# SPF: HELO does not publish an SPF Record
|
||||
set "t.SPF_HELO_NONE" "1";
|
||||
} elsif eval "env.spf_ehlo_result == 'softfail'" {
|
||||
# SPF: HELO does not match SPF record (softfail)
|
||||
set "t.SPF_HELO_SOFTFAIL" "1";
|
||||
} elsif eval "env.spf_ehlo_result == 'neutral'" {
|
||||
# SPF: HELO does not match SPF record (neutral)
|
||||
set "t.SPF_HELO_NEUTRAL" "1";
|
||||
}
|
||||
|
||||
# ARC checks
|
||||
if eval "!is_empty(env.arc_result)" {
|
||||
if "env.arc_result == 'pass'" {
|
||||
# Message has a valid ARC signature
|
||||
set "t.ARC_VALID" "1";
|
||||
}
|
||||
|
||||
if env.arc_result != 'none'" {
|
||||
# Message has a ARC signature
|
||||
set "t.ARC_SIGNED" "1";
|
||||
}
|
||||
}
|
||||
|
||||
# DMARC checks
|
||||
if eval "env.dmarc_result == 'pass'" {
|
||||
# DMARC pass policy
|
||||
set "t.DMARC_PASS" "1";
|
||||
} elsif eval "env.dmarc_policy == 'none'" {
|
||||
# Missing DMARC policy
|
||||
set "t.DMARC_MISSING" "1";
|
||||
} elsif eval "env.dmarc_result == 'fail'" {
|
||||
if eval "env.dmarc_policy == 'reject'" {
|
||||
# DMARC reject policy
|
||||
set "t.DMARC_REJECT" "1";
|
||||
} elsif eval "env.dmarc_policy == 'quarantine'" {
|
||||
# DMARC quarantine policy
|
||||
set "t.DMARC_QUAR" "1";
|
||||
} elsif eval "env.dmarc_policy == 'none'" {
|
||||
# DMARC none policy
|
||||
set "t.DMARC_NONE" "1";
|
||||
}
|
||||
}
|
||||
|
||||
# Recipient checks
|
||||
set "to_cc" "%{dedup(headers.to[*].addr[*] + headers.cc[*].addr[*])}";
|
||||
if eval "len(to_cc) >= 7 && sort(to_cc, true) == to_cc" {
|
||||
# Recipients are sorted alphabetically
|
||||
set "t.SORTED_RECIPS" "1";
|
||||
}
|
||||
if eval "len(to_cc) => 5" {
|
||||
set "i" "%{len(to_cc)}";
|
||||
set "hits" "0";
|
||||
set "combinations" "0";
|
||||
|
||||
while "i" {
|
||||
set "i" "%{i - 1}";
|
||||
set "j" "%{i}";
|
||||
while "j" {
|
||||
set "j" "%{j - 1}";
|
||||
set "a" "%{to_lowercase(to_cc[i])}";
|
||||
set "b" "%{to_lowercase(to_cc[j])}";
|
||||
|
||||
if "levenshtein_distance(email_part(a, 'local'), email_part(b, 'local')) < 3" {
|
||||
set "hits" "%{hits + 1}";
|
||||
}
|
||||
|
||||
set "a" "%{email_part(a, 'host')}";
|
||||
set "b" "%{email_part(b, 'host')}";
|
||||
|
||||
if "a != b && levenshtein_distance(a, b) < 4" {
|
||||
set "hits" "%{hits + 1}";
|
||||
}
|
||||
|
||||
set "combinations" "%{combinations + 1}";
|
||||
}
|
||||
}
|
||||
|
||||
if eval "hits / combinations > 0.65" {
|
||||
set "t.SUSPICIOUS_RECIPS" "1";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +328,6 @@ from-addr = "no-reply@__DOMAIN__"
|
||||
return-path = ""
|
||||
#hostname = "__HOST__"
|
||||
sign = ["rsa"]
|
||||
use-directory = "__SMTP_DIRECTORY__"
|
||||
|
||||
[sieve.limits]
|
||||
redirects = 3
|
||||
|
||||
Reference in New Issue
Block a user