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");
}
?>