From c8955284a2159f529ae108eb70ce1f9b3bbf061a Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 3 Jun 2021 08:02:03 +0200 Subject: [PATCH] [Rspamd] Create BCC plugin --- data/conf/postfix/main.cf | 5 +- data/conf/rspamd/dynmaps/bcc.php | 88 +++++++++++++++++++++ data/conf/rspamd/lua/rspamd.local.lua | 106 ++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 data/conf/rspamd/dynmaps/bcc.php diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index a1bcf578..c4704d56 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -161,8 +161,9 @@ virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps. virtual_gid_maps = static:5000 virtual_mailbox_base = /var/vmail/ virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf -recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf -sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf +# -- moved to rspamd on 2021-06-01 +#recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf +#sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf recipient_canonical_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf recipient_canonical_classes = envelope_recipient virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf diff --git a/data/conf/rspamd/dynmaps/bcc.php b/data/conf/rspamd/dynmaps/bcc.php new file mode 100644 index 00000000..3145feeb --- /dev/null +++ b/data/conf/rspamd/dynmaps/bcc.php @@ -0,0 +1,88 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +try { + $pdo = new PDO($dsn, $database_user, $database_pass, $opt); +} +catch (PDOException $e) { + error_log("BCC MAP SQL ERROR: " . $e . PHP_EOL); + http_response_code(501); + exit; +} + +function parse_email($email) { + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; + $a = strrpos($email, '@'); + return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); +} +if (!function_exists('getallheaders')) { + function getallheaders() { + if (!is_array($_SERVER)) { + return array(); + } + $headers = array(); + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) == 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + +// Read headers +$headers = getallheaders(); +// Get rcpt +$rcpt = $headers['Rcpt']; +// Get from +$from = $headers['From']; +// Remove tags +$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); +$from = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $from); + +try { + if (!empty($rcpt)) { + $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'rcpt' AND `local_dest` = :local_dest AND `active` = '1'"); + $stmt->execute(array( + ':local_dest' => $rcpt + )); + $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; + if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { + error_log("BCC MAP: returning ". $bcc_dest . " for " . $rcpt . PHP_EOL); + http_response_code(201); + echo trim($bcc_dest); + exit; + } + } + if (!empty($from)) { + $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'sender' AND `local_dest` = :local_dest AND `active` = '1'"); + $stmt->execute(array( + ':local_dest' => $from + )); + $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; + if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { + error_log("BCC MAP: returning ". $bcc_dest . " for " . $from . PHP_EOL); + http_response_code(201); + echo trim($bcc_dest); + exit; + } + } +} +catch (PDOException $e) { + error_log("BCC MAP SQL ERROR: " . $e->getMessage() . PHP_EOL); + http_response_code(502); + exit; +} + diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua index 3f4c326d..3c2aa7b2 100644 --- a/data/conf/rspamd/lua/rspamd.local.lua +++ b/data/conf/rspamd/lua/rspamd.local.lua @@ -320,6 +320,112 @@ rspamd_config:register_symbol({ priority = 19 }) +rspamd_config:register_symbol({ + name = 'BCC', + type = 'postfilter', + callback = function(task) + local util = require("rspamd_util") + local rspamd_http = require "rspamd_http" + local rspamd_logger = require "rspamd_logger" + + local from_table = {} + local rcpt_table = {} + + local send_mail = function(task, bcc_dest) + local lua_smtp = require "lua_smtp" + local function sendmail_cb(ret, err) + if not ret then + rspamd_logger.errx(task, 'BCC SMTP ERROR: %s', err) + else + rspamd_logger.infox(rspamd_config, "BCC SMTP SUCCESS TO %s", bcc_dest) + end + end + if not bcc_dest then + return -- stop + end + lua_smtp.sendmail({ + task = task, + host = 'postfix', + port = 591, + from = task:get_from(stp)[1].addr, + recipients = bcc_dest, + helo = 'bcc', + timeout = 10, + }, task:get_content(), sendmail_cb) + end + + -- determine from + local from = task:get_from('smtp') + if from then + for _, a in ipairs(from) do + table.insert(from_table, a['addr']) -- add this rcpt to table + table.insert(from_table, '@' .. a['domain']) -- add this rcpts domain to table + end + else + return -- stop + end + + -- determine rcpts + local rcpts = task:get_recipients('smtp') + if rcpts then + for _, a in ipairs(rcpts) do + table.insert(rcpt_table, a['addr']) -- add this rcpt to table + table.insert(rcpt_table, '@' .. a['domain']) -- add this rcpts domain to table + end + else + return -- stop + end + + local action = task:get_metric_action('default') + rspamd_logger.infox("metric action now: %s", action) + + local function rcpt_callback(err_message, code, body, headers) + if err_message == nil and code == 201 and body ~= nil then + if action == 'no action' or action == 'add header' or action == 'rewrite subject' then + send_mail(task, body) + end + end + end + + local function from_callback(err_message, code, body, headers) + if err_message == nil and code == 201 and body ~= nil then + if action == 'no action' or action == 'add header' or action == 'rewrite subject' then + send_mail(task, body) + end + end + end + + if rcpt_table then + for _,e in ipairs(rcpt_table) do + rspamd_logger.infox(rspamd_config, "checking bcc for rcpt address %s", e) + rspamd_http.request({ + task=task, + url='http://nginx:8081/bcc.php', + body='', + callback=rcpt_callback, + headers={Rcpt=e} + }) + end + end + + if from_table then + for _,e in ipairs(from_table) do + rspamd_logger.infox(rspamd_config, "checking bcc for from address %s", e) + rspamd_http.request({ + task=task, + url='http://nginx:8081/bcc.php', + body='', + callback=from_callback, + headers={From=e} + }) + end + end + + return true + end, + priority = 20 +}) + rspamd_config:register_symbol({ name = 'DYN_RL_CHECK', type = 'prefilter',