Merge branch 'master' of https://github.com/mailcow/mailcow-dockerized
This commit is contained in:
1
data/conf/rspamd/custom/.empty
Normal file
1
data/conf/rspamd/custom/.empty
Normal file
@@ -0,0 +1 @@
|
||||
1
|
25
data/conf/rspamd/custom/ratelimit.lua
Normal file
25
data/conf/rspamd/custom/ratelimit.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
local custom_keywords = {
|
||||
['customrl'] = {},
|
||||
}
|
||||
|
||||
function custom_keywords.customrl.get_value(task)
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
if task:has_symbol('DYN_RL') then
|
||||
rspamd_logger.infox(rspamd_config, "task has a dynamic ratelimit symbol, processing...")
|
||||
return "check"
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "task has no dynamic ratelimit symbol, skipping...")
|
||||
return
|
||||
end
|
||||
end
|
||||
function custom_keywords.customrl.get_limit(task)
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local dyn_rl_symbol = task:get_symbol("DYN_RL")
|
||||
if dyn_rl_symbol then
|
||||
local rl_value = dyn_rl_symbol[1].options[1]
|
||||
rspamd_logger.infox(rspamd_config, "dynamic ratelimit symbol has option %s, returning...", rl_value)
|
||||
return rl_value
|
||||
end
|
||||
end
|
||||
-- returning custom keywords
|
||||
return custom_keywords
|
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
ini_set('error_reporting', 0);
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
$stmt = $pdo->query("SELECT `domain` FROM `domain`");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
echo strtolower(trim($row['domain'])) . PHP_EOL;
|
||||
}
|
||||
$stmt = $pdo->query("SELECT `alias_domain` FROM `alias_domain`");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
echo strtolower(trim($row['alias_domain'])) . PHP_EOL;
|
||||
}
|
||||
?>
|
44
data/conf/rspamd/dynmaps/forwardinghosts.php
Normal file
44
data/conf/rspamd/dynmaps/forwardinghosts.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
header('Content-Type: text/plain');
|
||||
ini_set('error_reporting', 0);
|
||||
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis-mailcow', 6379);
|
||||
|
||||
function in_net($addr, $net) {
|
||||
$net = explode('/', $net);
|
||||
if (count($net) > 1) {
|
||||
$mask = $net[1];
|
||||
}
|
||||
$net = inet_pton($net[0]);
|
||||
$addr = inet_pton($addr);
|
||||
$length = strlen($net); // 4 for IPv4, 16 for IPv6
|
||||
if (strlen($net) != strlen($addr)) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($mask)) {
|
||||
$mask = $length * 8;
|
||||
}
|
||||
$addr_bin = '';
|
||||
$net_bin = '';
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT);
|
||||
$net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT);
|
||||
}
|
||||
return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask);
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
|
||||
if (in_net($_GET['host'], $host)) {
|
||||
echo '200 PERMIT';
|
||||
exit;
|
||||
}
|
||||
}
|
||||
echo '200 DUNNO';
|
||||
}
|
||||
catch (RedisException $e) {
|
||||
echo '200 DUNNO';
|
||||
exit;
|
||||
}
|
||||
?>
|
@@ -4,21 +4,105 @@ The match section performs AND operation on different matches: for example, if y
|
||||
then the rule matches only when from AND rcpt match. For similar matches, the OR rule applies: if you have multiple rcpt matches,
|
||||
then any of these will trigger the rule. If a rule is triggered then no more rules are matched.
|
||||
*/
|
||||
ini_set('error_reporting', '0');
|
||||
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
|
||||
ini_set('error_reporting', 0);
|
||||
|
||||
$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
try {
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
$stmt = $pdo->query("SELECT * FROM `filterconf`");
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
echo 'settings { }';
|
||||
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($email, $a));
|
||||
}
|
||||
|
||||
function ucl_rcpts($object, $type) {
|
||||
global $pdo;
|
||||
if ($type == 'mailbox') {
|
||||
// Standard aliases
|
||||
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
|
||||
WHERE `goto` LIKE :object_goto
|
||||
AND `address` NOT LIKE '@%'
|
||||
AND `address` != :object_address");
|
||||
$stmt->execute(array(
|
||||
':object_goto' => '%' . $object . '%',
|
||||
':object_address' => $object
|
||||
));
|
||||
$standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($standard_aliases)) {
|
||||
$local = parse_email($row['address'])['local'];
|
||||
$domain = parse_email($row['address'])['domain'];
|
||||
if (!empty($local) && !empty($domain)) {
|
||||
$rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
|
||||
}
|
||||
$rcpt[] = $row['address'];
|
||||
}
|
||||
// Aliases by alias domains
|
||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox`
|
||||
LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain`
|
||||
WHERE `mailbox`.`username` = :object");
|
||||
$stmt->execute(array(
|
||||
':object' => $object
|
||||
));
|
||||
$by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($by_domain_aliases);
|
||||
while ($row = array_shift($by_domain_aliases)) {
|
||||
if (!empty($row['alias'])) {
|
||||
$local = parse_email($row['alias'])['local'];
|
||||
$domain = parse_email($row['alias'])['domain'];
|
||||
if (!empty($local) && !empty($domain)) {
|
||||
$rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
|
||||
}
|
||||
$rcpt[] = $row['alias'];
|
||||
}
|
||||
}
|
||||
// Mailbox self
|
||||
$local = parse_email($row['object'])['local'];
|
||||
$domain = parse_email($row['object'])['domain'];
|
||||
if (!empty($local) && !empty($domain)) {
|
||||
$rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
|
||||
}
|
||||
$rcpt[] = $object;
|
||||
}
|
||||
elseif ($type == 'domain') {
|
||||
// Domain self
|
||||
$rcpt[] = '/.*@' . $object . '/i';
|
||||
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
|
||||
WHERE `target_domain` = :object");
|
||||
$stmt->execute(array(':object' => $object));
|
||||
$alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($alias_domains);
|
||||
while ($row = array_shift($alias_domains)) {
|
||||
$rcpt[] = '/.*@' . $row['alias_domain'] . '/i';
|
||||
}
|
||||
}
|
||||
if (!empty($rcpt)) {
|
||||
return $rcpt;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
?>
|
||||
settings {
|
||||
<?php
|
||||
|
||||
/*
|
||||
// Start custom scores for users
|
||||
*/
|
||||
|
||||
$stmt = $pdo->query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@@ -26,45 +110,18 @@ while ($row = array_shift($rows)) {
|
||||
$username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']);
|
||||
?>
|
||||
score_<?=$username_sane;?> {
|
||||
priority = low;
|
||||
priority = 4;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `option`, `value` FROM `filterconf`
|
||||
WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel')
|
||||
AND `object`= :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
|
||||
WHERE (`object`= :object OR `object`= :object_domain)
|
||||
AND (`option` = 'blacklist_from' OR `option` = 'whitelist_from')");
|
||||
$stmt->execute(array(':object' => $row['object'], ':object_domain' => substr(strrchr($row['object'], "@"), 1)));
|
||||
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
|
||||
?>
|
||||
from = "/^((?!<?=$value_sane;?>).)*$/";
|
||||
rcpt = "<?=$row['object'];?>";
|
||||
<?php
|
||||
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
|
||||
$stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object']));
|
||||
$rows_aliases_1 = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row_aliases_1 = array_shift($rows_aliases_1)) {
|
||||
?>
|
||||
rcpt = "<?=$row_aliases_1['address'];?>";
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox`
|
||||
LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain`
|
||||
WHERE `mailbox`.`username` = :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$rows_aliases_2 = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($rows_aliases_2);
|
||||
while ($row_aliases_2 = array_shift($rows_aliases_2)) {
|
||||
if (!empty($row_aliases_2['aliases'])) {
|
||||
?>
|
||||
rcpt = "<?=$row_aliases_2['aliases'];?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
actions {
|
||||
@@ -95,56 +152,76 @@ while ($row = array_shift($rows)) {
|
||||
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
|
||||
?>
|
||||
from = "/(<?=$value_sane;?>)/";
|
||||
from = "/(<?=$value_sane;?>)/i";
|
||||
<?php
|
||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
||||
?>
|
||||
priority = medium;
|
||||
rcpt = "/.*@<?=$row['object'];?>/";
|
||||
priority = 5;
|
||||
<?php
|
||||
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
|
||||
WHERE `target_domain` = :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$rows_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($rows_domain_aliases);
|
||||
while ($row_domain_aliases = array_shift($rows_domain_aliases)) {
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "/.*@<?=$row_domain_aliases['alias_domain'];?>/";
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
else {
|
||||
?>
|
||||
priority = high;
|
||||
rcpt = "<?=$row['object'];?>";
|
||||
priority = 6;
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
|
||||
$stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object']));
|
||||
$rows_aliases_wl_1 = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($rows_aliases_wl_1);
|
||||
while ($row_aliases_wl_1 = array_shift($rows_aliases_wl_1)) {
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "<?=$row_aliases_wl_1['address'];?>";
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox`
|
||||
LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain`
|
||||
WHERE `mailbox`.`username` = :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$rows_aliases_wl_2 = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($rows_aliases_wl_2);
|
||||
while ($row_aliases_wl_2 = array_shift($rows_aliases_wl_2)) {
|
||||
if (!empty($row_aliases_wl_2['aliases'])) {
|
||||
?>
|
||||
rcpt = "<?=$row_aliases_wl_2['aliases'];?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
MAILCOW_MOO = -999.0;
|
||||
MAILCOW_WHITE = -999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_WHITE"
|
||||
]
|
||||
}
|
||||
whitelist_header_<?=$username_sane;?> {
|
||||
<?php
|
||||
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
|
||||
WHERE `object`= :object
|
||||
AND `option` = 'whitelist_from'");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
|
||||
?>
|
||||
request_header = {
|
||||
"From" = "(<?=$value_sane;?>)";
|
||||
}
|
||||
<?php
|
||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
||||
?>
|
||||
priority = 5;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
else {
|
||||
?>
|
||||
priority = 6;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
MAILCOW_WHITE = -999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_WHITE"
|
||||
]
|
||||
}
|
||||
<?php
|
||||
}
|
||||
@@ -167,58 +244,78 @@ while ($row = array_shift($rows)) {
|
||||
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
|
||||
?>
|
||||
from = "/(<?=$value_sane;?>)/";
|
||||
from = "/(<?=$value_sane;?>)/i";
|
||||
<?php
|
||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
||||
?>
|
||||
priority = medium;
|
||||
rcpt = "/.*@<?=$row['object'];?>/";
|
||||
priority = 5;
|
||||
<?php
|
||||
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
|
||||
WHERE `target_domain` = :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$rows_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($rows_domain_aliases);
|
||||
while ($row_domain_aliases = array_shift($rows_domain_aliases)) {
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "/.*@<?=$row_domain_aliases['alias_domain'];?>/";
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
else {
|
||||
?>
|
||||
priority = high;
|
||||
rcpt = "<?=$row['object'];?>";
|
||||
priority = 6;
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%' AND `address` != :object_address");
|
||||
$stmt->execute(array(':object_goto' => $row['object'], ':object_address' => $row['object']));
|
||||
$rows_aliases_bl_1 = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($rows_aliases_bl_1);
|
||||
while ($row_aliases_bl_1 = array_shift($rows_aliases_bl_1)) {
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "<?=$row_aliases_bl_1['address'];?>";
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `aliases` FROM `mailbox`
|
||||
LEFT OUTER JOIN `alias_domain` on `mailbox`.`domain` = `alias_domain`.`target_domain`
|
||||
WHERE `mailbox`.`username` = :object");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$rows_aliases_bl_2 = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
array_filter($rows_aliases_bl_2);
|
||||
while ($row_aliases_bl_2 = array_shift($rows_aliases_bl_2)) {
|
||||
if (!empty($row_aliases_bl_2['aliases'])) {
|
||||
?>
|
||||
rcpt = "<?=$row_aliases_bl_2['aliases'];?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
MAILCOW_MOO = 999.0;
|
||||
MAILCOW_BLACK = 999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_BLACK"
|
||||
]
|
||||
}
|
||||
blacklist_header_<?=$username_sane;?> {
|
||||
<?php
|
||||
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(REPLACE(`value`, '*', '.*') SEPARATOR '|') AS `value` FROM `filterconf`
|
||||
WHERE `object`= :object
|
||||
AND `option` = 'blacklist_from'");
|
||||
$stmt->execute(array(':object' => $row['object']));
|
||||
$grouped_lists = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
$value_sane = preg_replace("/\.\./", ".", (preg_replace("/\*/", ".*", $grouped_lists[0])));
|
||||
?>
|
||||
request_header = {
|
||||
"From" = "(<?=$value_sane;?>)";
|
||||
}
|
||||
<?php
|
||||
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
|
||||
?>
|
||||
priority = 5;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
else {
|
||||
?>
|
||||
priority = 6;
|
||||
<?php
|
||||
foreach (ucl_rcpts($row['object'], strpos($row['object'], '@') === FALSE ? 'domain' : 'mailbox') as $rcpt) {
|
||||
?>
|
||||
rcpt = "<?=$rcpt;?>";
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
apply "default" {
|
||||
MAILCOW_BLACK = 999.0;
|
||||
}
|
||||
symbols [
|
||||
"MAILCOW_BLACK"
|
||||
]
|
||||
}
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
ini_set('error_reporting', 0);
|
||||
header('Content-Type: text/plain');
|
||||
require_once "vars.inc.php";
|
||||
$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
|
||||
$opt = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
|
||||
$stmt = $pdo->query("SELECT `username` FROM `mailbox` WHERE `wants_tagged_subject` = '1'");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
echo strtolower(trim($row['username'])) . PHP_EOL;
|
||||
}
|
||||
$stmt = $pdo->query("SELECT CONCAT(mailbox.local_part, '@', alias_domain.alias_domain) as `tag_ad` FROM `mailbox` INNER JOIN `alias_domain` ON mailbox.domain = alias_domain.target_domain WHERE mailbox.wants_tagged_subject='1';");
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while ($row = array_shift($rows)) {
|
||||
echo strtolower(trim($row['tag_ad'])) . PHP_EOL;
|
||||
}
|
||||
?>
|
@@ -1 +0,0 @@
|
||||
../../../web/inc/vars.inc.php
|
3
data/conf/rspamd/dynmaps/vars.inc.php
Normal file
3
data/conf/rspamd/dynmaps/vars.inc.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
require_once('../../../web/inc/vars.inc.php');
|
||||
?>
|
7
data/conf/rspamd/local.d/antivirus.conf
Normal file
7
data/conf/rspamd/local.d/antivirus.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
clamav {
|
||||
attachments_only = false;
|
||||
symbol = "CLAM_VIRUS";
|
||||
type = "clamav";
|
||||
log_clean = true;
|
||||
servers = "clamd:3310";
|
||||
}
|
30
data/conf/rspamd/local.d/arc.conf
Normal file
30
data/conf/rspamd/local.d/arc.conf
Normal file
@@ -0,0 +1,30 @@
|
||||
# If false, messages with empty envelope from are not signed
|
||||
allow_envfrom_empty = false;
|
||||
# If true, envelope/header domain mismatch is ignored
|
||||
allow_hdrfrom_mismatch = false;
|
||||
# If true, multiple from headers are allowed (but only first is used)
|
||||
allow_hdrfrom_multiple = true;
|
||||
# If true, username does not need to contain matching domain
|
||||
allow_username_mismatch = false;
|
||||
# If false, messages from authenticated users are not selected for signing
|
||||
auth_only = true;
|
||||
# Default path to key, can include '$domain' and '$selector' variables
|
||||
path = "/data/dkim/keys/$domain.dkim";
|
||||
# Default selector to use
|
||||
selector = "dkim";
|
||||
# If false, messages from local networks are not selected for signing
|
||||
sign_local = true;
|
||||
# Symbol to add when message is signed
|
||||
symbol = "ARC_SIGNED";
|
||||
# Whether to fallback to global config
|
||||
try_fallback = true;
|
||||
# Domain to use for DKIM signing: can be "header" or "envelope"
|
||||
use_domain = "envelope";
|
||||
# Whether to normalise domains to eSLD
|
||||
use_esld = false;
|
||||
# Whether to get keys from Redis
|
||||
use_redis = true;
|
||||
# Hash for DKIM keys in Redis
|
||||
key_prefix = "DKIM_PRIV_KEYS";
|
||||
# Selector map
|
||||
selector_prefix = "DKIM_SELECTORS";
|
@@ -1,34 +0,0 @@
|
||||
sign_condition =<<EOD
|
||||
return function(task)
|
||||
local smtp_from = task:get_from('smtp')
|
||||
local mime_from = task:get_from('mime')
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
if smtp_from[1]['domain'] ~= nil and smtp_from[1]['domain'] ~= '' then
|
||||
domain = smtp_from[1]['domain']
|
||||
rspamd_logger.infox(task, "set domain found in smtp from field to %s", domain)
|
||||
if not task:get_user() then
|
||||
rspamd_logger.infox(task, "found domain in smtp header field, but user is not authenticated - skipped")
|
||||
return false
|
||||
end
|
||||
elseif mime_from[1]['domain'] ~= nil and mime_from[1]['domain'] ~= '' then
|
||||
domain = mime_from[1]['domain']
|
||||
rspamd_logger.infox(task, "set domain found in mime from field to %s", domain)
|
||||
else
|
||||
rspamd_logger.infox(task, "cannot determine domain for dkim signing")
|
||||
return false
|
||||
end
|
||||
local keyfile = io.open("/data/dkim/keys/" .. domain .. ".dkim")
|
||||
if keyfile then
|
||||
rspamd_logger.infox(task, "found dkim key file for domain %s", domain)
|
||||
keyfile:close()
|
||||
return {
|
||||
key = "/data/dkim/keys/" .. domain .. ".dkim",
|
||||
domain = domain,
|
||||
selector = "dkim"
|
||||
}
|
||||
else
|
||||
rspamd_logger.infox(task, "no key file for domain %s - skipped", domain)
|
||||
end
|
||||
return false
|
||||
end
|
||||
EOD;
|
30
data/conf/rspamd/local.d/dkim_signing.conf
Normal file
30
data/conf/rspamd/local.d/dkim_signing.conf
Normal file
@@ -0,0 +1,30 @@
|
||||
# If false, messages with empty envelope from are not signed
|
||||
allow_envfrom_empty = false;
|
||||
# If true, envelope/header domain mismatch is ignored
|
||||
allow_hdrfrom_mismatch = false;
|
||||
# If true, multiple from headers are allowed (but only first is used)
|
||||
allow_hdrfrom_multiple = true;
|
||||
# If true, username does not need to contain matching domain
|
||||
allow_username_mismatch = true;
|
||||
# If false, messages from authenticated users are not selected for signing
|
||||
auth_only = true;
|
||||
# Default path to key, can include '$domain' and '$selector' variables
|
||||
path = "/data/dkim/keys/$domain.dkim";
|
||||
# Default selector to use
|
||||
selector = "dkim";
|
||||
# If false, messages from local networks are not selected for signing
|
||||
sign_local = true;
|
||||
# Symbol to add when message is signed
|
||||
symbol = "DKIM_SIGNED";
|
||||
# Whether to fallback to global config
|
||||
try_fallback = true;
|
||||
# Domain to use for DKIM signing: can be "header" or "envelope"
|
||||
use_domain = "envelope";
|
||||
# Whether to normalise domains to eSLD
|
||||
use_esld = false;
|
||||
# Whether to get keys from Redis
|
||||
use_redis = true;
|
||||
# Hash for DKIM keys in Redis
|
||||
key_prefix = "DKIM_PRIV_KEYS";
|
||||
# Selector map
|
||||
selector_prefix = "DKIM_SELECTORS";
|
22
data/conf/rspamd/local.d/force_actions.conf
Normal file
22
data/conf/rspamd/local.d/force_actions.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
rules {
|
||||
DKIM_FAIL {
|
||||
action = "add header";
|
||||
expression = "R_DKIM_REJECT & !MAILLIST & !MAILCOW_WHITE & !MAILCOW_BLACK";
|
||||
require_action = ["no action", "greylist"];
|
||||
}
|
||||
VIRUS_FOUND {
|
||||
action = "reject";
|
||||
expression = "CLAM_VIRUS & !MAILCOW_WHITE";
|
||||
honor_action = ["reject"];
|
||||
}
|
||||
WHITELIST_FORWARDING_HOST_NO_REJECT {
|
||||
action = "add header";
|
||||
expression = "WHITELISTED_FWD_HOST";
|
||||
require_action = ["reject"];
|
||||
}
|
||||
WHITELIST_FORWARDING_HOST_NO_GREYLIST {
|
||||
action = "no action";
|
||||
expression = "WHITELISTED_FWD_HOST";
|
||||
require_action = ["greylist", "soft reject"];
|
||||
}
|
||||
}
|
39
data/conf/rspamd/local.d/milter_headers.conf
Normal file
39
data/conf/rspamd/local.d/milter_headers.conf
Normal file
@@ -0,0 +1,39 @@
|
||||
use = ["spam-header", "x-spamd-result", "x-rspamd-queue-id", "authentication-results"];
|
||||
skip_local = false;
|
||||
skip_authenticated = true;
|
||||
routines {
|
||||
spam-header {
|
||||
header = "X-Spam-Flag";
|
||||
value = "YES";
|
||||
remove = 1;
|
||||
}
|
||||
authentication-results {
|
||||
header = "Authentication-Results";
|
||||
remove = 1;
|
||||
spf_symbols {
|
||||
pass = "R_SPF_ALLOW";
|
||||
fail = "R_SPF_FAIL";
|
||||
softfail = "R_SPF_SOFTFAIL";
|
||||
neutral = "R_SPF_NEUTRAL";
|
||||
temperror = "R_SPF_DNSFAIL";
|
||||
none = "R_SPF_NA";
|
||||
permerror = "R_SPF_PERMFAIL";
|
||||
}
|
||||
dkim_symbols {
|
||||
pass = "R_DKIM_ALLOW";
|
||||
fail = "R_DKIM_REJECT";
|
||||
temperror = "R_DKIM_TEMPFAIL";
|
||||
none = "R_DKIM_NA";
|
||||
permerror = "R_DKIM_PERMFAIL";
|
||||
}
|
||||
dmarc_symbols {
|
||||
pass = "DMARC_POLICY_ALLOW";
|
||||
permerror = "DMARC_BAD_POLICY";
|
||||
temperror = "DMARC_DNSFAIL";
|
||||
none = "DMARC_NA";
|
||||
reject = "DMARC_POLICY_REJECT";
|
||||
softfail = "DMARC_POLICY_SOFTFAIL";
|
||||
quarantine = "DMARC_POLICY_QUARANTINE";
|
||||
}
|
||||
}
|
||||
}
|
34
data/conf/rspamd/local.d/mime_types.conf
Normal file
34
data/conf/rspamd/local.d/mime_types.conf
Normal file
@@ -0,0 +1,34 @@
|
||||
# Extensions that are treated as 'bad'
|
||||
# Number is score multiply factor
|
||||
bad_extensions = {
|
||||
scr = 4,
|
||||
lnk = 4,
|
||||
exe = 1,
|
||||
jar = 2,
|
||||
com = 4,
|
||||
bat = 4,
|
||||
ace = 4,
|
||||
arj = 4,
|
||||
cab = 3,
|
||||
};
|
||||
|
||||
# Extensions that are particularly penalized for archives
|
||||
bad_archive_extensions = {
|
||||
pptx = 0.5,
|
||||
docx = 0.5,
|
||||
xlsx = 0.5,
|
||||
pdf = 1.0,
|
||||
jar = 3,
|
||||
js = 0.5,
|
||||
vbs = 7,
|
||||
};
|
||||
|
||||
# Used to detect another archive in archive
|
||||
archive_extensions = {
|
||||
zip = 1,
|
||||
arj = 1,
|
||||
rar = 1,
|
||||
ace = 1,
|
||||
7z = 1,
|
||||
cab = 1,
|
||||
};
|
22
data/conf/rspamd/local.d/multimap.conf
Normal file
22
data/conf/rspamd/local.d/multimap.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
RCPT_MAILCOW_DOMAIN {
|
||||
type = "rcpt";
|
||||
filter = "email:domain"
|
||||
map = "redis://DOMAIN_MAP"
|
||||
}
|
||||
|
||||
RCPT_WANTS_SUBJECT_TAG {
|
||||
type = "rcpt";
|
||||
filter = "email:addr"
|
||||
map = "redis://RCPT_WANTS_SUBJECT_TAG"
|
||||
}
|
||||
|
||||
WHITELISTED_FWD_HOST {
|
||||
type = "ip";
|
||||
map = "redis://WHITELISTED_FWD_HOST"
|
||||
}
|
||||
|
||||
KEEP_SPAM {
|
||||
type = "ip";
|
||||
map = "redis://KEEP_SPAM"
|
||||
action = "accept";
|
||||
}
|
7
data/conf/rspamd/local.d/mx_check.conf
Normal file
7
data/conf/rspamd/local.d/mx_check.conf
Normal file
@@ -0,0 +1,7 @@
|
||||
timeout = 1.0;
|
||||
symbol_bad_mx = "MX_INVALID";
|
||||
symbol_no_mx = "MX_MISSING";
|
||||
symbol_good_mx = "MX_GOOD";
|
||||
expire = 86400;
|
||||
key_prefix = "rmx";
|
||||
enabled = true;
|
@@ -1,3 +1,9 @@
|
||||
dns {
|
||||
enable_dnssec = true;
|
||||
enable_dnssec = true;
|
||||
}
|
||||
map_watch_interval = 15s;
|
||||
dns {
|
||||
timeout = 4s;
|
||||
retransmits = 5;
|
||||
}
|
||||
disable_monitored = true;
|
||||
|
@@ -1 +1,5 @@
|
||||
# rspamd.conf.local
|
||||
history_redis {}
|
||||
worker "log_helper" {
|
||||
count = 1;
|
||||
}
|
||||
|
@@ -7,69 +7,104 @@ rspamd_config.MAILCOW_AUTH = {
|
||||
end
|
||||
}
|
||||
|
||||
rspamd_config.MAILCOW_MOO = function (task)
|
||||
return true
|
||||
end
|
||||
|
||||
local modify_subject_map = rspamd_config:add_map({
|
||||
url = 'http://nginx:8081/tags.php',
|
||||
type = 'map',
|
||||
description = 'Map of users to use subject tags for'
|
||||
})
|
||||
|
||||
local auth_domain_map = rspamd_config:add_map({
|
||||
url = 'http://nginx:8081/authoritative.php',
|
||||
type = 'map',
|
||||
description = 'Map of domains we are authoritative for'
|
||||
})
|
||||
|
||||
rspamd_config.ADD_DELIMITER_TAG = {
|
||||
rspamd_config:register_symbol({
|
||||
name = 'TAG_MOO',
|
||||
type = 'postfilter',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
|
||||
local user_env_tagged = task:get_recipients(1)[1]['user']
|
||||
local user_to_tagged = task:get_recipients(2)[1]['user']
|
||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
||||
|
||||
local domain = task:get_recipients(1)[1]['domain']
|
||||
if tagged_rcpt and mailcow_domain then
|
||||
local tag = tagged_rcpt[1].options[1]
|
||||
rspamd_logger.infox("found tag: %s", tag)
|
||||
local action = task:get_metric_action('default')
|
||||
rspamd_logger.infox("metric action now: %s", action)
|
||||
|
||||
local user_env, tag_env = user_env_tagged:match("([^+]+)+(.*)")
|
||||
local user_to, tag_to = user_to_tagged:match("([^+]+)+(.*)")
|
||||
if action ~= 'no action' and action ~= 'greylist' then
|
||||
rspamd_logger.infox("skipping tag handler for action: %s", action)
|
||||
task:set_metric_action('default', action)
|
||||
return true
|
||||
end
|
||||
|
||||
local authdomain = auth_domain_map:get_key(domain)
|
||||
local wants_subject_tag = task:get_symbol("RCPT_WANTS_SUBJECT_TAG")
|
||||
|
||||
if tag_env then
|
||||
tag = tag_env
|
||||
user = user_env
|
||||
elseif tag_to then
|
||||
tag = tag_to
|
||||
user = user_env
|
||||
end
|
||||
|
||||
if tag and authdomain then
|
||||
rspamd_logger.infox("Domain %s is part of mailcow, start reading tag settings", domain)
|
||||
local user_untagged = user .. '@' .. domain
|
||||
rspamd_logger.infox("Querying tag settings for user %1", user_untagged)
|
||||
if modify_subject_map:get_key(user_untagged) then
|
||||
rspamd_logger.infox("User wants subject modified for tagged mail")
|
||||
if wants_subject_tag then
|
||||
rspamd_logger.infox("user wants subject modified for tagged mail")
|
||||
local sbj = task:get_header('Subject')
|
||||
if tag then
|
||||
rspamd_logger.infox("Found tag %1, will modify subject header", tag)
|
||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||
task:set_rmilter_reply({
|
||||
remove_headers = {['Subject'] = 1},
|
||||
add_headers = {['Subject'] = new_sbj}
|
||||
})
|
||||
end
|
||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||
task:set_milter_reply({
|
||||
remove_headers = {['Subject'] = 1},
|
||||
add_headers = {['Subject'] = new_sbj}
|
||||
})
|
||||
else
|
||||
rspamd_logger.infox("Add X-Moo-Tag header")
|
||||
task:set_rmilter_reply({
|
||||
task:set_milter_reply({
|
||||
add_headers = {['X-Moo-Tag'] = 'YES'}
|
||||
})
|
||||
end
|
||||
else
|
||||
rspamd_logger.infox("Skip delimiter handling for untagged message or authenticated user")
|
||||
end
|
||||
return false
|
||||
end
|
||||
}
|
||||
end,
|
||||
priority = 11
|
||||
})
|
||||
|
||||
rspamd_config:register_symbol({
|
||||
name = 'DYN_RL_CHECK',
|
||||
type = 'prefilter',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local redis_params = rspamd_parse_redis_server('dyn_rl')
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
local envfrom = task:get_from(1)
|
||||
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
|
||||
local env_from_addr = envfrom[1].addr:lower() -- get smtp from addr in lower case
|
||||
|
||||
local function redis_cb_user(err, data)
|
||||
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for user %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying dynamic ratelimit for domain...", env_from_addr, data, err)
|
||||
|
||||
local function redis_key_cb_domain(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for domain %s returned invalid or empty data (\"%s\") or error (\"%s\")", env_from_domain, data, err)
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for domain %s with value %s", env_from_domain, data)
|
||||
task:insert_result('DYN_RL', 0.0, data)
|
||||
end
|
||||
end
|
||||
|
||||
local redis_ret_domain = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
env_from_domain, -- hash key
|
||||
false, -- is write
|
||||
redis_key_cb_domain, --callback
|
||||
'HGET', -- command
|
||||
{'RL_VALUE', env_from_domain} -- arguments
|
||||
)
|
||||
if not redis_ret_domain then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for domain")
|
||||
end
|
||||
else
|
||||
rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for user %s with value %s", env_from_addr, data)
|
||||
task:insert_result('DYN_RL', 0.0, data)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local redis_ret_user = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
env_from_addr, -- hash key
|
||||
false, -- is write
|
||||
redis_cb_user, --callback
|
||||
'HGET', -- command
|
||||
{'RL_VALUE', env_from_addr} -- arguments
|
||||
)
|
||||
if not redis_ret_user then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for user")
|
||||
end
|
||||
return true
|
||||
end,
|
||||
priority = 20
|
||||
})
|
@@ -5,3 +5,4 @@ secure_ip = "172.16.0.0/12";
|
||||
secure_ip = "10.0.0.0/8";
|
||||
secure_ip = "127.0.0.1";
|
||||
secure_ip = "::1";
|
||||
secure_ip = "fd4d:6169:6c63:6f77::/64"
|
||||
|
7
data/conf/rspamd/override.d/worker-proxy.inc
Normal file
7
data/conf/rspamd/override.d/worker-proxy.inc
Normal file
@@ -0,0 +1,7 @@
|
||||
bind_socket = "rspamd:9900";
|
||||
milter = true;
|
||||
upstream {
|
||||
name = "localhost";
|
||||
default = true;
|
||||
hosts = "rspamd:11333"
|
||||
}
|
Reference in New Issue
Block a user