1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2026-06-18 20:40:38 +00:00

Merge pull request #7283 from mailcow/feat/update-metadata-exporter

[Rspamd] Migrate metadata_exporter to multipart formatter
This commit is contained in:
FreddleSpl0it
2026-06-11 09:40:34 +02:00
committed by GitHub
4 changed files with 52 additions and 63 deletions
@@ -3,8 +3,7 @@ rules {
backend = "http"; backend = "http";
url = "http://nginx:9081/pipe.php"; url = "http://nginx:9081/pipe.php";
selector = "reject_no_global_bl"; selector = "reject_no_global_bl";
formatter = "default"; formatter = "multipart";
meta_headers = true;
} }
RLINFO { RLINFO {
backend = "http"; backend = "http";
@@ -16,8 +15,7 @@ rules {
backend = "http"; backend = "http";
url = "http://nginx:9081/pushover.php"; url = "http://nginx:9081/pushover.php";
selector = "mailcow_rcpt"; selector = "mailcow_rcpt";
formatter = "json"; formatter = "multipart";
meta_headers = true;
} }
} }
+27 -32
View File
@@ -32,47 +32,42 @@ function parse_email($email) {
$a = strrpos($email, '@'); $a = strrpos($email, '@');
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
} }
if (!function_exists('getallheaders')) { // rspamd metadata_exporter (multipart formatter):
function getallheaders() { // - $_POST['metadata'] JSON with the rspamd metadata
if (!is_array($_SERVER)) { // - $_FILES['message'] raw RFC822 message
return array(); if (empty($_POST['metadata']) || !isset($_FILES['message']) || $_FILES['message']['error'] !== UPLOAD_ERR_OK) {
} error_log("QUARANTINE: missing multipart parts from rspamd" . PHP_EOL);
$headers = array(); http_response_code(400);
foreach ($_SERVER as $name => $value) { exit;
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
return $headers;
}
} }
$raw_data_content = file_get_contents('php://input'); $meta = json_decode($_POST['metadata'], true);
if (!is_array($meta)) {
error_log("QUARANTINE: cannot decode metadata JSON" . PHP_EOL);
http_response_code(400);
exit;
}
$raw_data_content = file_get_contents($_FILES['message']['tmp_name']);
$raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8"); $raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8");
$headers = getallheaders(); $raw_size = (int)$_FILES['message']['size'];
$qid = $headers['X-Rspamd-Qid']; $qid = $meta['qid'] ?? 'unknown';
$fuzzy = $headers['X-Rspamd-Fuzzy']; $subject = iconv_mime_decode($meta['subject'] ?? '');
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']); $score = $meta['score'] ?? 0;
$score = $headers['X-Rspamd-Score']; $rcpts = $meta['rcpt'] ?? array();
$rcpts = $headers['X-Rspamd-Rcpt']; $user = $meta['user'] ?? 'unknown';
$user = $headers['X-Rspamd-User']; $ip = $meta['ip'] ?? 'unknown';
$ip = $headers['X-Rspamd-Ip']; $action = $meta['action'] ?? 'no action';
$action = $headers['X-Rspamd-Action']; $sender = $meta['from'] ?? '';
$sender = $headers['X-Rspamd-From']; $symbols = json_encode($meta['symbols'] ?? array());
$symbols = $headers['X-Rspamd-Symbols']; $fuzzy = json_encode(is_array($meta['fuzzy'] ?? null) ? $meta['fuzzy'] : array());
$raw_size = (int)$_SERVER['CONTENT_LENGTH'];
if (empty($sender)) { if (empty($sender)) {
error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL); error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL);
$sender = 'empty-env-from@localhost'; $sender = 'empty-env-from@localhost';
} }
if ($fuzzy == 'unknown') {
$fuzzy = '[]';
}
try { try {
$max_size = (int)$redis->Get('Q_MAX_SIZE'); $max_size = (int)$redis->Get('Q_MAX_SIZE');
if (($max_size * 1048576) < $raw_size) { if (($max_size * 1048576) < $raw_size) {
@@ -94,7 +89,7 @@ catch (RedisException $e) {
$rcpt_final_mailboxes = array(); $rcpt_final_mailboxes = array();
// Loop through all rcpts // Loop through all rcpts
foreach (json_decode($rcpts, true) as $rcpt) { foreach ($rcpts as $rcpt) {
// Remove tag // Remove tag
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
+22 -26
View File
@@ -32,50 +32,46 @@ function parse_email($email) {
$a = strrpos($email, '@'); $a = strrpos($email, '@');
return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1));
} }
if (!function_exists('getallheaders')) { // rspamd metadata_exporter (multipart formatter): metadata JSON arrives as $_POST['metadata'].
function getallheaders() { if (empty($_POST['metadata'])) {
if (!is_array($_SERVER)) { error_log("NOTIFY: missing metadata part from rspamd" . PHP_EOL);
return array(); http_response_code(400);
} exit;
$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;
}
} }
$headers = getallheaders(); $meta = json_decode($_POST['metadata'], true);
$json_body = json_decode(file_get_contents('php://input')); if (!is_array($meta)) {
error_log("NOTIFY: cannot decode metadata JSON" . PHP_EOL);
http_response_code(400);
exit;
}
$qid = $headers['X-Rspamd-Qid']; $qid = $meta['qid'] ?? 'unknown';
$rcpts = $headers['X-Rspamd-Rcpt']; $rcpts = $meta['rcpt'] ?? array();
$sender = $headers['X-Rspamd-From']; $sender = $meta['from'] ?? '';
$ip = $headers['X-Rspamd-Ip']; $ip = $meta['ip'] ?? 'unknown';
$subject = iconv_mime_decode($headers['X-Rspamd-Subject']); $subject = iconv_mime_decode($meta['subject'] ?? '');
$messageid= $json_body->message_id; $messageid= $meta['message_id'] ?? '';
$priority = 0; $priority = 0;
$symbols_array = json_decode($headers['X-Rspamd-Symbols'], true); $symbols_array = $meta['symbols'] ?? array();
if (is_array($symbols_array)) { if (is_array($symbols_array)) {
foreach ($symbols_array as $symbol) { foreach ($symbols_array as $symbol) {
if ($symbol['name'] == 'HAS_X_PRIO_ONE') { if (($symbol['name'] ?? null) == 'HAS_X_PRIO_ONE') {
$priority = 1; $priority = 1;
break; break;
} }
} }
} }
$sender_address = $json_body->header_from[0]; $sender_address = $meta['header_from'][0] ?? '';
$sender_name = '-'; $sender_name = '-';
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) { if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $sender_address, $matches)) {
$sender_address = $matches['address']; $sender_address = $matches['address'];
$sender_name = trim($matches['name'], '"\' '); $sender_name = trim($matches['name'], '"\' ');
} }
$to_address = $json_body->header_to[0]; $to_address = $meta['header_to'][0] ?? '';
$to_name = '-'; $to_name = '-';
if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) { if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
$to_address = $matches['address']; $to_address = $matches['address'];
@@ -85,7 +81,7 @@ if (preg_match('/(?<name>.*?)<(?<address>.*?)>/i', $to_address, $matches)) {
$rcpt_final_mailboxes = array(); $rcpt_final_mailboxes = array();
// Loop through all rcpts // Loop through all rcpts
foreach (json_decode($rcpts, true) as $rcpt) { foreach ($rcpts as $rcpt) {
// Remove tag // Remove tag
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
+1 -1
View File
@@ -84,7 +84,7 @@ services:
- clamd - clamd
rspamd-mailcow: rspamd-mailcow:
image: ghcr.io/mailcow/rspamd:3.14.3-1 image: ghcr.io/mailcow/rspamd:4.1.0-1
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
- dovecot-mailcow - dovecot-mailcow