One recent project I’ve been working on lately is implementing more aggressive spam blocking for our company. Over the last two years, the amount of spam we are receiving has exploded. Then, about 2 months ago, I found out about SpamAssassin.
SpamAssassin is, at it’s simplest, a smart filtering program written in Perl and released under the GNU license agreement. Unfortunately for me, it is written primarily to run under Unix or Linux or other similar platforms.
We are primarily a Windows Server shop. But, thanks to Michael Bell and his excellent Using SpamAssassin with Win32 guide, I was able to get it running under Windows. (I’ve been writing Perl code for a lonnggg time now so I was very familiar with that aspect of it.) But this was the easy part.
The hard part was, using the tools that come with Windows 2000 Server, find a way to pump the stuff through SpamAssassin before delivering it. We use Microsoft IIS SMTP Server as a relay to our real MTA. All the filtering would happen on the IIS Server.
Originally, I took the SpamAssassin code and created a DLL with it using the PerlCtrl object from the Perl Development Kit. This became a COM object that could be invoked by using a CreateObject() call, then invoking the method to check the message. Then, I created a VB SMTP Sink that hooked the OnMessageArrival Sink in the MS SMTP Server. The problem was that when you write an SMTP Sink in VB, it serializes the calls and you can quickly run out of resources if you process several messages at once.
After trying this for a few hours, I noticed that the SMTP server was using over 400Mb of RAM and running like a pig. So, VB was out.
I decided to write a C++ sink that hooks the OnMessageSubmission Sink, which runs asynchronously. The problem with this was after several calls to the SpamAssassinator DLL (using the IUnknown and Invoke-> calls) I was starting to get BSTR corruption. My Sink was compiled as free-threaded, but the SpamAssassinator DLL was only apartment-thread safe.
Basically, this would make the IISAdmin service crash every 15 minutes or so.
Finally, I decided to go low tech. I re-wrote my SMTP Sink to check, when it receives a message, if it was received from TCP-IP transport or from the mail-pickup folder. If it was from TCP-IP, I would mark the message as undeliverable and write it out to a watched folder. (My sink was based on the Archiver demo sink at the Microsoft site.)
Then, I wrote a Perl program that watched that folder, invoked SpamAssassin on any new messages, then (based on the score) re-write the message into the IIS SMTP Pickup directory. Then, I used the PerlSvc to compile this into a Windows Service.
Now, I simply turn on this service and let it run. So far, it looks like 85-90 percent of our incoming messages are spam (but you have to remember, this is our backup MX server, which spammers tend to target.)
I’ve set the threshhold to be a bit low (4.5) but it’s working very well.
And, I’ve named it SpamAssassinator. Pretty hokey, but it works!