Score:0

Using PostFix address rewriting to entirely remove a recipient from an outgoing email in a relay

pn flag

I am trying to use postfix to entirely remove a particular recipient entirely from the "to" or "cc" fields of an email, but have not figure out how to do so yet.

I have postfix configured as a relay host. I am using it to relay from Exchange on Office 365 to smtp.gmail.com. This is to allow a specific user to send from their Office 365 account out of their old gmail.com email address. We have an outbound connector in Exchange set up to route to the postfix relay server, and a rule set to send this user's outbound mail to the connector.

The postfix relay is then set up to use normal SMTP AUTH to relay mail to smtp.gmail.com.

This all works perfectly. Say the user's gmail is [email protected] and their exchange mailbox is [email protected]. To send their '[email protected]' mail to their Office 365 account, we have a simple forwarder set up in gmail to forward all mail to user@domain.com.

The one issue we're trying to improve, is if the user replies all to any of the forwarded mail in the exchange inbox using Outlook, their [email protected] address will show up as a "To" recipient. Because the original mail was sent to their user@gmail address, and that mail was then forwarded to [email protected], Outlook connect to [email protected] thinks their gmail address is another user to be replied to. I don't know any way to stop Outlook from doing this.

To keep them from continually mailing themselves, we just want to use a simple rule in the postfix relay to remove themselves from the "To" (or "CC") fields. I've set up a canonical rule on recipients in main.cf:

recipient_canonical_maps = hash:/etc/postfix/recipient_canonical

And then I'm trying to get the canonical ap to replace [email protected] with.... something that will delete it entirely out of the email's recipients.

I can get the desired rewrite to match [email protected] in the To field, but I cannot for the life of me figure out a hash or regexp rule (if I switch to regex mapping) that will remove the email address entirely. I've tried a blank, which postmap (when I try to create a db) complains is not a valid key whitespace value entry. Anyone have any luck using rules to entirely remove a particular recipient from an email?

Please note I cross-posted this on reddit as well because I cannot find anything related to removal (instead of just rewriting) recipients anywhere: https://www.reddit.com/r/postfix/comments/128plg8/using_postfix_address_rewriting_to_entirely/

Score:0
pn flag

For those interested, my solution was to create an after-queue content filter to be called to remove the [email protected] address from both the recipients for the message as well as from the "To:" and "Cc:" email headers in the message. The former is required so that we don't send an actual email to [email protected], and the latter is just so [email protected] doesn't show up in those headers when the email arrives at the recipients.

Content added to 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

And mail-cleaner.php, which uses the ZBateson MailMimeParser to cleanly modify the email headers.

#!/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");
}

?>

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.