Beau's PHP 5 mail() logging Solution for Windows Server 2003

When I set out to stop spammers from exploiting poorly written scripts scattered across various web sites hosted on a Windows server, I had no idea that I was in for such a long journey. I will try to lay out the steps I took in order to solve the problem, which I am happy to report is working well.

The Problem:
PHP as of 5.2.1 provides no way of tracking which script, or even which web site is the source of an email being delivered using PHP's mail() function. Because so many web applications use it, I didn't want to disable it. Furthermore, the idea of combing through all of the code on multiple sites was not intriguing. Furthermore, I can't change someone else's code and I don't want to start isolating customers by turning their scripts off.

The Platform:
Using a Windows 2003 Server and PHP 5.1.2, I thought this would be quick. Basically, the fix isn't that difficult, but finding the solution led me to many options for Linux / Apache, but few for Windows.

The Details:
I first discovered the option of patching mail.c, thanks to Ilia Alshanetsky's blog at http://ilia.ws/archives/149-mail-logging-for-PHP.html.

I wanted to provide similar functionality for Windows, but did not want to recompile PHP in a Windows environment. The patch added code so that every script sent from the server adds x-header information, such as the script and site sending it, as well as logging what goes out.

The Solution:
(PHP Sendmail Wrapper Script + Fake Sendmail + php.ini)
I found a few other alternatives until finally deciding on a unique combination of tools to get the job done. First off, if you are not going to change users code, or patch and recompile the PHP source, you have to have some method of capturing the email leaving the web site, but before it gets to the mail server. In comes Sendmail Wrapper from Greg Maclellan (www.gregmaclellan.com)and Fake Sendmail from Byron Jones (http://glob.com.au/sendmail/.)

The Sendmail Wrapper PHP script is a great little script that will accept mail just as if it were sendmail and then deliver it to the real sendmail (or fake sendmail in my case). The clever part here is that you can specify this script as the default mail server in your PHP.INI file. For Windows, just disable the smtp port, host, etc.. and put c:usrlibsendmail_wrapper.php  as your  path to sendmail. (Yes, specifying the sendmail path will work on Windows).

Setting up Sendmail for Windows was fairly straight forward. The executable sits in c:usrlibsendmail and includes an ini file that lets you forward mail to your real SMTP server and specify a few other options, including a folder to log the messages. Sendmail simply captures the email piped to it from a script.

Putting the two together was fairly easy. I modified the sendmail wrapper script to use the path to sendmail and made sure fake sendmail was up and running. I also decided since I could let fake sendmail do authentication against an SMTP server to put an actual mail account on my mail server specifically for handling the mail processed via php and I added some limitations, one being a quota no more than 500 messages per day.

The final piece of the puzzle was getting PHP to add some extra headers beyond what the Sendmail Wrapper script was providing so I would really have some robust information. I decided to use Environment Variables and PHP's append directive in php.ini to set some variables up on each page. This idea is credited to Harold Paulson who posted on
Greg Maclellan's blog. I made a page called prepend.php and put in in my php append path (or prepend, you choose). I added the following information to mine, although you could really capture anything you wanted to add to the message header).

<?
putenv("REMOTE_ADDR=" . $_SERVER['REMOTE_ADDR']);
putenv("SCRIPT_NAME=" . $_SERVER['SCRIPT_NAME']);
putenv("SERVER_NAME=" . $_SERVER['SERVER_NAME']);
?>

Next, I made a modification to the sendmail_wrapper script to include this information in the headers of the mail.

// additional headers
$add_headers["X-MsgID"] = $messageid;
$add_headers["X-SenderIP"] = $_ENV["REMOTE_ADDR"];
$add_headers["X-WebSite"] = $_ENV["SERVER_NAME"];
$add_headers["X-Script"] = $_ENV["SCRIPT_NAME"];

I replaced the existing line (below) in the script with the above.

if (preg_match("|C:/Program Files/Plexus/Sites/([a-zA-Z0-9._-]*)(/.*)?|", $_ENV["PWD"], $matches)) {
    $add_headers["X-Generating-Domain"] = $matches[1];
}


A few tips on making this work:

Be certain you have assigned permissions for the user that PHP is running as to have access to execute this script. Also, I believe in PHP 5 you still need to make sure cmd.exe has execute permissions to call the script. Finally, you must execute it using php.exe the CLI (Command Line Interface), not php-cgi.exe or the script will have trouble reading the STDIN input. If you ran the PHP binary installer when you originally installed PHP, head over to http://snaps.php.net/ and download the zip file, which will contain php.exe. Better yet, it might be a good day to upgrade anyway. Don't forget to restart IIS after you make the changes to php.ini.


Read and post comments | Send to a friend

Comments

Popular posts from this blog

Apple TV - Recover from nothing Take 2

Convert a LiveCycle Form back to an Acrobat Form

Converting a MySQL database to SQLite on a Mac