git subrepo commit (merge) mailcow/src/mailcow-dockerized
subrepo: subdir: "mailcow/src/mailcow-dockerized"
merged: "c7b1dc37"
upstream: origin: "https://github.com/mailcow/mailcow-dockerized.git"
branch: "master"
commit: "a366494c"
git-subrepo: version: "0.4.6"
origin: "???"
commit: "???"
Change-Id: Id574ecd4e02e3c4fbf8a1efd49be11c0b6d19a3f
diff --git a/mailcow/src/mailcow-dockerized/data/conf/rspamd/custom/bad_asn.map b/mailcow/src/mailcow-dockerized/data/conf/rspamd/custom/bad_asn.map
index 1858c55..a8d49cf 100644
--- a/mailcow/src/mailcow-dockerized/data/conf/rspamd/custom/bad_asn.map
+++ b/mailcow/src/mailcow-dockerized/data/conf/rspamd/custom/bad_asn.map
@@ -27,4 +27,5 @@
#197518 2 #Rackmarkt SL, Spain
#197695 2 #Domain names registrar REG.RU Ltd, Russia
#198068 2 #P.A.G.M. OU, Estonia
-#201942 5 #Soltia Consulting SL, Spain
\ No newline at end of file
+#201942 5 #Soltia Consulting SL, Spain
+#213373 4 #IP Connect Inc
\ No newline at end of file
diff --git a/mailcow/src/mailcow-dockerized/data/conf/rspamd/local.d/composites.conf b/mailcow/src/mailcow-dockerized/data/conf/rspamd/local.d/composites.conf
index 337a2eb..e6fa24c 100644
--- a/mailcow/src/mailcow-dockerized/data/conf/rspamd/local.d/composites.conf
+++ b/mailcow/src/mailcow-dockerized/data/conf/rspamd/local.d/composites.conf
@@ -8,7 +8,7 @@
}
# Bad policy from free mail providers
FREEMAIL_POLICY_FAILURE {
- expression = "-g+:policies & !DMARC_POLICY_ALLOW & !MAILLIST & ( FREEMAIL_ENVFROM | FREEMAIL_FROM ) & !WHITELISTED_FWD_HOST";
+ expression = "FREEMAIL_FROM & !DMARC_POLICY_ALLOW & !MAILLIST& !WHITELISTED_FWD_HOST & -g+:policies";
score = 16.0;
}
# Applies to freemail with undisclosed recipients
@@ -68,3 +68,53 @@
ENCRYPTED_CHAT {
expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP";
}
+# Remove bayes ham if fuzzy denied
+FUZZY_HAM_MISMATCH {
+ expression = "( -FUZZY_DENIED | -MAILCOW_FUZZY_DENIED | -LOCAL_FUZZY_DENIED ) & ( ^BAYES_HAM | ^NEURAL_HAM_LONG | ^NEURAL_HAM_SHORT )";
+}
+# Remove bayes spam if local fuzzy white
+FUZZY_SPAM_MISMATCH {
+ expression = "( -LOCAL_FUZZY_WHITE ) & ( ^BAYES_SPAM | ^NEURAL_SPAM_LONG | ^NEURAL_SPAM_SHORT )";
+}
+WL_FWD_HOST {
+ expression = "-WHITELISTED_FWD_HOST & (^g+:rbl | ^g+:policies | ^g+:hfilter | ^g:neural)";
+}
+ENCRYPTED_CHAT {
+ expression = "CHAT_VERSION_HEADER & ENCRYPTED_PGP";
+}
+
+CLAMD_SPAM_FOUND {
+ expression = "CLAM_SECI_SPAM & !MAILCOW_WHITE";
+ description = "Probably Spam, Securite Spam Flag set through ClamAV";
+ score = 5;
+}
+
+CLAMD_BAD_PDF {
+ expression = "CLAM_SECI_PDF & !MAILCOW_WHITE";
+ description = "Bad PDF Found, Securite bad PDF Flag set through ClamAV";
+ score = 8;
+}
+
+CLAMD_BAD_JPG {
+ expression = "CLAM_SECI_JPG & !MAILCOW_WHITE";
+ description = "Bad JPG Found, Securite bad JPG Flag set through ClamAV";
+ score = 8;
+}
+
+CLAMD_ASCII_MALWARE {
+ expression = "CLAM_SECI_ASCII & !MAILCOW_WHITE";
+ description = "ASCII malware found, Securite ASCII malware Flag set through ClamAV";
+ score = 8;
+}
+
+CLAMD_HTML_MALWARE {
+ expression = "CLAM_SECI_HTML & !MAILCOW_WHITE";
+ description = "HTML malware found, Securite HTML malware Flag set through ClamAV";
+ score = 8;
+}
+
+CLAMD_JS_MALWARE {
+ expression = "CLAM_SECI_JS & !MAILCOW_WHITE";
+ description = "JS malware found, Securite JS malware Flag set through ClamAV";
+ score = 8;
+}
\ No newline at end of file
diff --git a/mailcow/src/mailcow-dockerized/data/conf/rspamd/local.d/multimap.conf b/mailcow/src/mailcow-dockerized/data/conf/rspamd/local.d/multimap.conf
index 17ada99..888bf36 100644
--- a/mailcow/src/mailcow-dockerized/data/conf/rspamd/local.d/multimap.conf
+++ b/mailcow/src/mailcow-dockerized/data/conf/rspamd/local.d/multimap.conf
@@ -159,8 +159,8 @@
}
URLHAUS_ABUSE_CH {
- type = "url";
- filter = "full";
+ type = "selector";
+ selector = "urls";
map = "https://urlhaus.abuse.ch/downloads/text_online/";
score = 10.0;
}
@@ -175,7 +175,7 @@
type = "header";
header = "subject";
regexp = true;
- map = "http://nullnull.org/bad-subject-regex.txt";
+ map = "http://fuzzy.mailcow.email/bad-subject-regex.txt";
score = 6.0;
symbols_set = ["BAD_SUBJECT_00"];
}
diff --git a/mailcow/src/mailcow-dockerized/data/conf/rspamd/lua/rspamd.local.lua b/mailcow/src/mailcow-dockerized/data/conf/rspamd/lua/rspamd.local.lua
index 6318bd2..acc4055 100644
--- a/mailcow/src/mailcow-dockerized/data/conf/rspamd/lua/rspamd.local.lua
+++ b/mailcow/src/mailcow-dockerized/data/conf/rspamd/lua/rspamd.local.lua
@@ -221,6 +221,16 @@
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
+ local function remove_moo_tag()
+ local moo_tag_header = task:get_header('X-Moo-Tag', false)
+ if moo_tag_header then
+ task:set_milter_reply({
+ remove_headers = {['X-Moo-Tag'] = 0},
+ })
+ end
+ return true
+ end
+
if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
local tag = tagged_rcpt[1].options[1]
rspamd_logger.infox("found tag: %s", tag)
@@ -229,6 +239,7 @@
if action ~= 'no action' and action ~= 'greylist' then
rspamd_logger.infox("skipping tag handler for action: %s", action)
+ remove_moo_tag()
return true
end
@@ -243,6 +254,7 @@
local function tag_callback_subfolder(err, data)
if err or type(data) ~= 'string' then
rspamd_logger.infox(rspamd_config, "subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
+ remove_moo_tag()
else
rspamd_logger.infox("Add X-Moo-Tag header")
task:set_milter_reply({
@@ -261,6 +273,7 @@
)
if not redis_ret_subfolder then
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
+ remove_moo_tag()
end
else
@@ -268,7 +281,10 @@
local sbj = task:get_header('Subject')
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
task:set_milter_reply({
- remove_headers = {['Subject'] = 1},
+ remove_headers = {
+ ['Subject'] = 1,
+ ['X-Moo-Tag'] = 0
+ },
add_headers = {['Subject'] = new_sbj}
})
end
@@ -284,6 +300,7 @@
)
if not redis_ret_subject then
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
+ remove_moo_tag()
end
end
@@ -295,6 +312,7 @@
if #rcpt_split == 2 then
if rcpt_split[1] == 'postmaster' then
rspamd_logger.infox(rspamd_config, "not expanding postmaster alias")
+ remove_moo_tag()
else
rspamd_http.request({
task=task,
@@ -307,7 +325,8 @@
end
end
end
-
+ else
+ remove_moo_tag()
end
end,
priority = 19
@@ -340,6 +359,10 @@
if not bcc_dest then
return -- stop
end
+ -- dot stuff content before sending
+ local email_content = tostring(task:get_content())
+ email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
+ -- send mail
lua_smtp.sendmail({
task = task,
host = os.getenv("IPV4_NETWORK") .. '.253',
@@ -347,8 +370,8 @@
from = task:get_from(stp)[1].addr,
recipients = bcc_dest,
helo = 'bcc',
- timeout = 10,
- }, task:get_content(), sendmail_cb)
+ timeout = 20,
+ }, email_content, sendmail_cb)
end
-- determine from
@@ -499,3 +522,146 @@
end
end
})
+
+rspamd_config:register_symbol({
+ name = 'MOO_FOOTER',
+ type = 'prefilter',
+ callback = function(task)
+ local lua_mime = require "lua_mime"
+ local lua_util = require "lua_util"
+ local rspamd_logger = require "rspamd_logger"
+ local rspamd_redis = require "rspamd_redis"
+ local ucl = require "ucl"
+ local redis_params = rspamd_parse_redis_server('footer')
+ local envfrom = task:get_from(1)
+ local uname = task:get_user()
+ if not envfrom or not uname then
+ return false
+ end
+ local uname = uname:lower()
+ local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
+
+ local function newline(task)
+ local t = task:get_newlines_type()
+
+ if t == 'cr' then
+ return '\r'
+ elseif t == 'lf' then
+ return '\n'
+ end
+
+ return '\r\n'
+ end
+ local function redis_cb_footer(err, data)
+ if err or type(data) ~= 'string' then
+ rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
+ else
+ -- parse json string
+ local parser = ucl.parser()
+ local res,err = parser:parse_string(data)
+ if not res then
+ rspamd_logger.infox(rspamd_config, "parsing domain wide footer for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err)
+ else
+ local footer = parser:get_object()
+
+ if footer and type(footer) == "table" and (footer.html or footer.plain) then
+ rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s", uname, footer.html, footer.plain)
+
+ local envfrom_mime = task:get_from(2)
+ local from_name = ""
+ if envfrom_mime and envfrom_mime[1].name then
+ from_name = envfrom_mime[1].name
+ elseif envfrom and envfrom[1].name then
+ from_name = envfrom[1].name
+ end
+
+ local replacements = {
+ auth_user = uname,
+ from_user = envfrom[1].user,
+ from_name = from_name,
+ from_addr = envfrom[1].addr,
+ from_domain = envfrom[1].domain:lower()
+ }
+ if footer.html then
+ footer.html = lua_util.jinja_template(footer.html, replacements, true)
+ end
+ if footer.plain then
+ footer.plain = lua_util.jinja_template(footer.plain, replacements, true)
+ end
+
+ -- add footer
+ local out = {}
+ local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {}
+
+ local seen_cte
+ local newline_s = newline(task)
+
+ local function rewrite_ct_cb(name, hdr)
+ if rewrite.need_rewrite_ct then
+ if name:lower() == 'content-type' then
+ local nct = string.format('%s: %s/%s; charset=utf-8',
+ 'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype)
+ out[#out + 1] = nct
+ return
+ elseif name:lower() == 'content-transfer-encoding' then
+ out[#out + 1] = string.format('%s: %s',
+ 'Content-Transfer-Encoding', 'quoted-printable')
+ seen_cte = true
+ return
+ end
+ end
+ out[#out + 1] = hdr.raw:gsub('\r?\n?$', '')
+ end
+
+ task:headers_foreach(rewrite_ct_cb, {full = true})
+
+ if not seen_cte and rewrite.need_rewrite_ct then
+ out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable')
+ end
+
+ -- End of headers
+ out[#out + 1] = newline_s
+
+ if rewrite.out then
+ for _,o in ipairs(rewrite.out) do
+ out[#out + 1] = o
+ end
+ else
+ out[#out + 1] = task:get_rawbody()
+ end
+ local out_parts = {}
+ for _,o in ipairs(out) do
+ if type(o) ~= 'table' then
+ out_parts[#out_parts + 1] = o
+ out_parts[#out_parts + 1] = newline_s
+ else
+ out_parts[#out_parts + 1] = o[1]
+ if o[2] then
+ out_parts[#out_parts + 1] = newline_s
+ end
+ end
+ end
+ task:set_message(out_parts)
+ else
+ rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\")", uname, data)
+ end
+ end
+ end
+ end
+
+ local redis_ret_footer = rspamd_redis_make_request(task,
+ redis_params, -- connect params
+ env_from_domain, -- hash key
+ false, -- is write
+ redis_cb_footer, --callback
+ 'HGET', -- command
+ {"DOMAIN_WIDE_FOOTER", env_from_domain} -- arguments
+ )
+ if not redis_ret_footer then
+ rspamd_logger.infox(rspamd_config, "cannot make request to load footer for domain")
+ end
+
+ return true
+ end,
+ priority = 1
+})