mail() in PHP and bounces

Everybody’s favorite way of sending spam: the mail() function in PHP. But what about the bounced e-mails?

Yeah, about them. Where do they get sent back? Well, not so fast. Not always to the “From” address of the original e-mail, that’s for sure. The e-mail servers return e-mails to the “envelope sender” which may or may not be the same as “From”. There used to be a special header, “Errors-To”, but it got deprecated and nowadays may simply be ignored.

Anyway, let’s go back to the “envelope sender”; this is set by the smtp client when connecting to the server and one would expect that the mail() from PHP be smart enough to deal with it. Let’s see how this function really works. First, let’s take a look in php.ini:

[mail function]
; For Win32 only.
SMTP = localhost
smtp_port = 25

; For Win32 only.
sendmail_from = me@example.com

; For Unix only. You may supply arguments as well (default: "sendmail -t -i").
sendmail_path = /usr/sbin/sendmail -t -i

On Windows it looks like mail() really handles everything by itself. On Linux it looks like the work is delegated to an external component – sendmail. Let’s take a look in the sendmail man page (well, actually the one from Postfix):

-i  When reading a message from standard input, don´t treat a line with only a . character as the end of input.

-t  Extract recipients from message headers. These are added to any recipients specified on the command line.

It looks like mail() fixes up the e-mail contents and then pipes it to sendmail; but what about the “envelope sender”? The sendmail man page is even more revealing:

-f sender  Set the envelope sender address. This is the address where delivery problems are sent to. With Postfix versions before 2.1, the Errors-To: message header overrides the error return address.

No -f parameter is provided in this case; this means that bounces get sent to one of the following:

  • for older e-mail systems, to the Errors-To: header e-mail address (if any provided in the 4th parameter of the mail() function);
  • to the Return-Path: header e-mail address (if any provided in the 4th parameter of the mail() function);
  • to the e-mail address that was automatically determined for the local user that executed the sendmail command (e.g. apache, nobody). Postfix stores it in the X-Postfix-Sender: header (added automatically).

If we want to avoid the 3rd scenario we must do some programming work. First, let’s notice that in php.ini one could change the e-mail sending program with a script of our own that can do some magic and then call sendmail:

sendmail_path = /usr/local/sbin/sendmail_wrapper

The script should obviously have the right permissions:

rwxr-xr-x  1 root root 814 Oct 4 15:45 sendmail_wrapper

One may write such script in Python or Ruby; PHP works wonderfully, though, as long as you do not call mail() in the script:

#!/usr/bin/php -q
<?php
set_time_limit(0);

$mtext = "";
$from = "";
$has_return_path = false;
// Feel free to replace regex with a better e-mail match
$regex="/[a-z0-9]+([_\\.-][a-z0-9]+)*@([a-z0-9]+([\.-][a-z0-9]+)*)+\\.[a-z]{2,}/i";
$stdin = fopen('php://stdin', 'r');
while(!feof($stdin))
{
  $cline=fgets($stdin);

  if (!((strpos($cline, "Return-Path:")===false)))
    if (preg_match_all($regex, $cline, $matches, PREG_PATTERN_ORDER))
      $has_return_path = true;

  if (!((strpos($cline, "From:")===false)))
    if (preg_match_all($regex, $cline, $matches, PREG_PATTERN_ORDER))
      $from = $matches[0][0];

  $mtext.=$cline;
}
fclose($stdin);
if (!empty($from) && !$has_return_path) $from_opt = ' -f'.$from;
$fd=popen('/usr/sbin/sendmail -t -i'.$from_opt, 'w');
fwrite($fd, $mtext);
pclose($fd);
?>

Well, that’s about it. I hope you enjoyed it.

Leave a Reply

Your email address will not be published. Required fields are marked *