使用 PostFix 地址重写从中继发送的电子邮件中彻底删除收件人

使用 PostFix 地址重写从中继发送的电子邮件中彻底删除收件人

我正在尝试使用 postfix 从电子邮件的“收件人”或“抄送”字段中完全删除特定收件人,但还没有弄清楚如何操作。

我将 postfix 配置为中继主机。我使用它从 Office 365 上的 Exchange 中继到smtp.gmail.com。这是为了允许特定用户从他们的 Office 365 帐户通过他们的旧gmail.com电子邮件地址发送邮件。我们在 Exchange 中设置了一个出站连接器以路由到 postfix 中继服务器,并设置了一条规则以将此用户的出站邮件发送到该连接器。

然后设置 Postfix 中继以使用正常 SMTP AUTH 将邮件中继到smtp.gmail.com

这一切都很完美。假设用户的 gmail 是[email protected],他们的 exchange 邮箱是[email protected]。要发送他们的 '[电子邮件保护]'邮件到他们的 Office 365 帐户,我们在 gmail 中设置了一个简单的转发器,将所有邮件转发给 user@ domain.com

我们正在尝试改进的一个问题是,如果用户使用 Outlook 回复 Exchange 收件箱中的任何转发邮件,他们的[email protected]地址将显示为“收件人”。由于原始邮件已发送到他们的user@gmail address,并且该邮件随后被转发到[email protected],因此 Outlook 连接到 会[email protected]认为他们的 gmail 地址是另一个需要回复的用户。我不知道有什么方法可以阻止 Outlook 这样做。

为了防止他们不断给自己发邮件,我们只想在 postfix 中继中使用一条简单规则,将他们自己从“收件人”(或“抄送”)字段中删除。我在 main.cf 中为收件人设置了一条规范规则:

recipient_canonical_maps = hash:/etc/postfix/recipient_canonical

然后我尝试用[email protected]...某种东西来替换规范的 ap,以便将其从电子邮件收件人中彻底删除。

我可以得到所需的重写以匹配[email protected]收件人字段,但我无论如何也想不出一个哈希或正则表达式规则(如果我切换到正则表达式映射),它将消除电子邮件地址。我尝试过空白,当我尝试创建数据库时,postmap 会抱怨这不是有效key whitespace value条目。有人使用规则从电子邮件中完全删除特定收件人吗?

请注意,我也在 reddit 上交叉发布了此内容,因为我在任何地方都找不到与删除(而不仅仅是重写)收件人相关的任何内容:https://www.reddit.com/r/postfix/comments/128plg8/using_postfix_address_rewriting_to_entirely/

答案1

对于那些感兴趣的人,我的解决方案是创建一个队列后内容过滤器,调用该过滤器[email protected]从邮件的收件人以及邮件中的“收件人:”和“抄送:”电子邮件标头中删除地址。前者是必需的,这样我们就不会向发送实际的电子邮件[email protected],而后者只是为了[email protected]在电子邮件到达收件人时不会出现在这些标头中。

master.cf 中添加的内容:

myhook unix - n n - - pipe
  flags=F user=filter argv=/etc/postfix/mail-cleaner/mail-cleaner.php ${sender} ${size} ${recipient}

smtp      inet  n       -       -       -       -       smtpd
        -o content_filter=myhook:dummy

还有 mail-cleaner.php,它使用 ZBateson MailMimeParser 来干净地修改电子邮件标题。

#!/usr/bin/php
<?php

require_once 'vendor/autoload.php';

use ZBateson\MailMimeParser\MailMimeParser;
use ZBateson\MailMimeParser\Message;
use ZBateson\MailMimeParser\Header\Part\HeaderPartFactory;
use ZBateson\MailMimeParser\Header\Part\MimeLiteralPartFactory;
use ZBateson\MailMimeParser\Header\HeaderFactory;
use ZBateson\MailMimeParser\Header\AddressHeader;
use ZBateson\MailMimeParser\Header\Consumer\ConsumerService;
use ZBateson\MbWrapper\MbWrapper;

$removeAddresses = ["[email protected]", "[email protected]"];

$fileOut = fopen("/tmp/postfix/postfixtest-".date("Y-m-d"), "a");

fwrite($fileOut, "==============================================\n");
fwrite($fileOut, "New running at ".date("Y-m-d H:i:s")."\n");
fwrite($fileOut, "Receipients to remove: ".implode(",",$removeAddresses)."\n");

// Print the command line argument
for ($i = 1; $i < count($argv); $i++) {
    fwrite($fileOut, "  Arg $i: " . $argv[$i] . "\n");
}

// Get receipients
$args = $argv; // Copy all command line arguments to $args array
$from = $argv[1];

array_shift($args); // Remove first argument which is the script name
array_shift($args); // Remove second argument
$recepients = array_slice($args, 1); // Copy the third and all the rest of command line arguments into $result array

fwrite($fileOut, "From: ".$from."\nReceipients: ".implode(",",$recepients)."\n");

// Remove receipients
foreach ($recepients as $key => $value) {
    foreach ($removeAddresses as $remove) {
        if (strpos($value, $remove) !== false) {
            fwrite($fileOut, "    Removing: ".$recepients[$key]."\n");
            unset($recepients[$key]);
        }
    }
}

fwrite($fileOut, " -> New recipients: ".implode(",",$recepients)."\n");

// Read the email from stdin
$email = stream_get_contents(STDIN);

// Parse the email using MailMimeParser
$parser = new MailMimeParser();
$message = $parser->parse($email, true);

fwrite($fileOut,"Message-ID: ".$message->getHeader("Message-ID")->getRawValue()."\nSubject: ".$message->getHeader("Subject")->getRawValue()."\n");

$mbWrapper = new MbWrapper();
$headerPartFactory = new HeaderPartFactory($mbWrapper);
$mimeLiteralPartFactory = new MimeLiteralPartFactory($mbWrapper);

$consumerService = new ConsumerService($headerPartFactory, $mimeLiteralPartFactory);

$headerFactory = new HeaderFactory($consumerService, $mimeLiteralPartFactory);

// Get the "To" header
removeAddress($consumerService, $message, "To", $removeAddresses, $fileOut);
removeAddress($consumerService, $message, "Cc", $removeAddresses, $fileOut);

// Send the modified email using sendmail
$sendmailPath = '/usr/sbin/sendmail'; // or wherever your sendmail binary is located
$sendmailArgs = '-G -i '.implode(' ',$recepients);

fwrite($fileOut,"Calling Sendmail: ".$sendmailPath." ".$sendmailArgs."\n");

$process = proc_open("$sendmailPath $sendmailArgs", [
    0 => ['pipe', 'r'], // stdin
    1 => ['pipe', 'w'], // stdout
    2 => ['pipe', 'w'], // stderr
], $pipes);

if (is_resource($process)) {
    $message->save($pipes[0]);
    fclose($pipes[0]);
    $stdout = stream_get_contents($pipes[1]);
    fclose($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[2]);
    $exitCode = proc_close($process);
    if ($exitCode !== 0) {
        $err = "ERROR: Sendmail failed with exit code $exitCode:\n$stdout\n$stderr";
        fwrite($fileOut,$err."\n");
        throw new Exception($err);
    }
} else {
    $err = "Failed to start sendmail process";
    fwrite($fileOut,$err."\n");
    throw new Exception($err);
}

fwrite($fileOut,"-> Success\n");
fclose($fileOut);

exit(0);

// Function to remove values from header
function removeAddress($consumerService, $message, $fieldName, $removeAddresses, $fileOut) { 
    $header = $message->getHeader($fieldName);

    if ($header === null) {
        fwrite($fileOut,$fieldName ." is empty\n");
        return;
    }
    
    if ($header instanceof AddressHeader) {
        $addresses = $header->getAddresses();
        
        fwrite($fileOut, $fieldName.": ".implode(",", $addresses)."\n");
        
        foreach ($addresses as $key => $value) {
            foreach ($removeAddresses as $remove) {
                if (strpos($value, $remove) !== false) {
                    fwrite($fileOut, "    Removing: ".$addresses[$key]."\n");
                    unset($addresses[$key]);
                }
            }
        }
        
        $message->removeHeader($fieldName);

        if (count($addresses) > 0) {
            $newHeader = new AddressHeader($consumerService,$fieldName,implode(",",$addresses));
            $message->setRawHeader($fieldName, $newHeader->getRawValue());
            fwrite($fileOut, " -> New ".$fieldName.": ".$message->getHeader($fieldName)->getRawValue()."\n");
        }
        else {
            fwrite($fileOut, " -> ".$fieldName." is now empty\n");
        }
        
        return;
    }
    throw new Exception($fieldName." header invalid");
}

?>

相关内容