public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
[Web] Important fixes for quarantaine; other minor changes
003e6ef5
data/conf/rspamd/dynmaps/settings.php | 6 +-
data/conf/rspamd/meta_exporter/pipe.php | 123 +++++++++++++++++++++++++-------
docker-compose.yml | 2 +-
3 files changed, 100 insertions(+), 31 deletions(-)
Diff
diff --git a/data/conf/rspamd/dynmaps/settings.php b/data/conf/rspamd/dynmaps/settings.php
index bcb82264..99a72a53 100644
--- a/data/conf/rspamd/dynmaps/settings.php
+++ b/data/conf/rspamd/dynmaps/settings.php
@@ -47,7 +47,7 @@ function ucl_rcpts($object, $type) {
$local = parse_email($row['address'])['local'];
$domain = parse_email($row['address'])['domain'];
if (!empty($local) && !empty($domain)) {
- $rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
+ $rcpt[] = '/' . $local . '[+].*' . $domain . '/i';
}
$rcpt[] = $row['address'];
}
@@ -65,7 +65,7 @@ function ucl_rcpts($object, $type) {
$local = parse_email($row['alias'])['local'];
$domain = parse_email($row['alias'])['domain'];
if (!empty($local) && !empty($domain)) {
- $rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
+ $rcpt[] = '/' . $local . '[+].*' . $domain . '/i';
}
$rcpt[] = $row['alias'];
}
@@ -74,7 +74,7 @@ function ucl_rcpts($object, $type) {
$local = parse_email($row['object'])['local'];
$domain = parse_email($row['object'])['domain'];
if (!empty($local) && !empty($domain)) {
- $rcpt[] = '/' . $local . '\+.*' . $domain . '/i';
+ $rcpt[] = '/' . $local . '[+].*' . $domain . '/i';
}
$rcpt[] = $object;
}
diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php
index 1075032c..31f5daac 100644
--- a/data/conf/rspamd/meta_exporter/pipe.php
+++ b/data/conf/rspamd/meta_exporter/pipe.php
@@ -77,36 +77,103 @@ catch (RedisException $e) {
exit;
}
-$filtered_rcpts = array();
+$rcpt_final_mailboxes = array();
+
+// Loop through all rcpts
foreach (json_decode($rcpts, true) as $rcpt) {
- $parsed_mail = parse_email($rcpt);
- if (in_array($parsed_mail['domain'], $exclude_domains)) {
- error_log(sprintf("Skipped domain %s", $parsed_mail['domain']));
+ // Break rcpt into local part and domain part
+ $parsed_rcpt = parse_email($rcpt);
+
+ // Skip if not a mailcow handled domain
+ try {
+ if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) {
+ continue;
+ }
+ }
+ catch (RedisException $e) {
+ error_log($e);
+ http_response_code(504);
+ exit;
+ }
+
+ // Skip if domain is excluded
+ if (in_array($parsed_rcpt['domain'], $exclude_domains)) {
+ error_log(sprintf("Skipped domain %s", $parsed_rcpt['domain']));
continue;
}
+
+ // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases
+ //
+ // rcpt
+ // |
+ // mailbox <-- goto ---> alias1, alias2, mailbox2
+ // | |
+ // mailbox3 |
+ // |
+ // alias3 ---> mailbox4
+ //
try {
- $stmt = $pdo->prepare("SELECT `goto` FROM `alias`
- WHERE
- (
- `address` = :rcpt
- OR
- `address` IN (
- SELECT username FROM mailbox, alias_domain
- WHERE (alias_domain.alias_domain = :domain_part
- AND mailbox.username = CONCAT(:local_part, '@', alias_domain.target_domain)
- AND mailbox.active = '1'
- AND alias_domain.active='1')
- )
- )
- AND `active`= '1';");
+ $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
$stmt->execute(array(
- ':rcpt' => $rcpt,
- ':local_part' => $parsed_mail['local'],
- ':domain_part' => $parsed_mail['domain']
+ ':rcpt' => $rcpt
));
$gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
- if (!empty($gotos)) {
- $filtered_rcpts = array_unique(array_merge($filtered_rcpts, explode(',', $gotos)));
+ if (empty($gotos)) {
+ $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'");
+ $stmt->execute(array(
+ ':rcpt' => '@' . $parsed_rcpt['domain']
+ ));
+ $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
+ }
+ $gotos_array = explode(',', $gotos);
+
+ $loop_c = 0;
+
+ while (count($gotos_array) != 0 && $loop_c <= 20) {
+
+ // Loop through all found gotos
+ foreach ($gotos_array as $index => &$goto) {
+ error_log("quarantaine pipe: query " . $goto . " as username from mailbox");
+ $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND `active`= '1';");
+ $stmt->execute(array(':goto' => $goto));
+ $username = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
+ if (!empty($username)) {
+ error_log("quarantaine pipe: mailbox found: " . $username);
+ // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate
+ if (!in_array($username, $rcpt_final_mailboxes)) {
+ $rcpt_final_mailboxes[] = $username;
+ }
+ }
+ else {
+ $parsed_goto = parse_email($goto);
+ if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) {
+ error_log($goto . " is not a mailcow handled mailbox or alias address");
+ }
+ else {
+ $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'");
+ $stmt->execute(array(':goto' => $goto));
+ $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
+ error_log("quarantaine pipe: goto address " . $goto . " is a alias branch for " . $goto_branch);
+ $goto_branch_array = explode(',', $goto_branch);
+ }
+ }
+ // goto item was processed, unset
+ unset($gotos_array[$index]);
+ }
+
+ // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array
+ if (!empty($goto_branch_array)) {
+ $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array));
+ unset($goto_branch_array);
+ }
+
+ // Reindex array
+ $gotos_array = array_values($gotos_array);
+
+ // Force exit if loop cannot be solved
+ // Postfix does not allow for alias loops, so this should never happen.
+ $loop_c++;
+ error_log("quarantaine pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array));
}
}
catch (PDOException $e) {
@@ -115,8 +182,9 @@ foreach (json_decode($rcpts, true) as $rcpt) {
exit;
}
}
-foreach ($filtered_rcpts as $rcpt) {
-
+
+foreach ($rcpt_final_mailboxes as $rcpt) {
+ error_log("quarantaine pipe: processing quarantaine message for rcpt " . $rcpt);
try {
$stmt = $pdo->prepare("INSERT INTO `quarantaine` (`qid`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`)
VALUES (:qid, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action)");
@@ -131,18 +199,19 @@ foreach ($filtered_rcpts as $rcpt) {
':msg' => $raw_data,
':action' => $action
));
- $stmt = $pdo->prepare('DELETE FROM `quarantaine` WHERE `id` NOT IN (
+ $stmt = $pdo->prepare('DELETE FROM `quarantaine` WHERE `rcpt` = :rcpt AND `id` NOT IN (
SELECT `id`
FROM (
SELECT `id`
FROM `quarantaine`
- WHERE `rcpt` = :rcpt
+ WHERE `rcpt` = :rcpt2
ORDER BY id DESC
LIMIT :retention_size
) x
);');
$stmt->execute(array(
':rcpt' => $rcpt,
+ ':rcpt2' => $rcpt,
':retention_size' => $retention_size
));
}
diff --git a/docker-compose.yml b/docker-compose.yml
index 8eea2f8f..59bc7cd2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -91,7 +91,7 @@ services:
- rspamd
php-fpm-mailcow:
- image: mailcow/phpfpm:1.7
+ image: mailcow/phpfpm:1.8
build: ./data/Dockerfiles/phpfpm
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on: