Postfix

Converting from Procmail to Maildrop

I’ve been using procmail to filter mail on the server forever. I like it, so it’s important to note that even though I switched, I have nothing bad to say about procmail.

So, why did I switch? Procmail can be a little terse to read (obviously, I’m used to it by now). Over the years, I have built a large set of rules. There is a ton of cruft in there. If I wanted to clean it up, I had to rewrite it. Rewriting it in procmail was definitely a possibility.

But, over the years, I was also aware of maildrop as a filtering solution. It has a cleaner (more accurately, a more straightforward) syntax. The documentation is a little sparse (missing a few key examples IMHO). There are also thousands (if not millions!) of example lines of procmail available on the net, and it can be hard to find complex real-world examples of maildrop filters.

But, I knew that if I rebuilt my filters in maildrop, I’d be forced to rethink everything, since I couldn’t get lazy and just grab hunks of procmail from my current system and plop them into the new one. So, maildrop it was going to be!

One last time, just to make sure I don’t offend lovers of procmail (of which I am one!), everything that I did in maildrop could easily have been done in procmail. I just happened to choose maildrop for this rewrite, and for now, will stick with it. Perhaps if I ever revisit this project, I’ll iterate the next time back in procmail.

The goal of my filters is to toss obvious spam (send it to /dev/null). Likely spam gets sorted into one of three IMAP folders. The only reason I split it into multiple folders is so that I can test rules before turning them into full-blow deletes. Finally, mail that falls through those is delivered to my inbox.

Over the years, I added numerous rules to filter classes of spam (stocks, watches, viagra, insurance, etc.). Without a doubt, I introduced tons of redundancy. I didn’t scan all of the previous rules to see where I might be able to add one more line because it would be too tedious vs just adding a new rule.

I was reasonably satisfied with the result, but over time, became less aggressive about deleting mail automatically, preferring to stuff it in a spam folder for visual scanning during the day.

Since I create my own rules (I don’t run a system like SpamAssassin, which I did for a while), I can start to see patterns and simplifications over time, which was the impetus for the rewrite. In other words, there are more commonalities across classes of spam, and I don’t have to spend as much time categorizing things as I was bothering to do.

I’ve now made my first cut of the maildrop-based system. It’s been in production now for seven days, and I’m very happy with it so far. The one major change I made is to default to deleting things (in other words, much more aggressive than the previous system), but, I keep a copy of all mail in an archive IMAP folder that I will prune through a cron job, and never scan visually.

I review my delete logs once a day, so if I spot an email that looks like I shouldn’t have deleted it, or someone contacts me asking why I didn’t respond, I will be able to check the archive and have the full mail there (for some reasonable period of time).

Here’s the result of the rewrite:

The original procmail system had roughly 3800 lines it in (including comments and blank lines). The new maildrop system has under 550 lines, including comments and blanks. I delete more mail automatically, and in a week, haven’t deleted a single mail that I didn’t mean to. I am getting a few more emails sneaking into my inbox, but each day, I add a few more lines and the list gets shorter the next day.

Now that I am getting a bit more spam each day into my inbox, Thunderbird junk filters are getting more to train on, and they are getting better too, so even the junk that is getting in, is mostly getting filed in the Junk folder locally, automatically.

Here are two things that took me longer than they should have to figure out with maildrop (they are related, meaning the solution is identical in both cases, but it wasn’t obvious to me):

  • How to negate a test using !
  • How to use weighted scoring correctly (very simple in procmail)

Here’s a line in maildrop format:

if ($TESTVAR =~ /123/)

do something useful if true…

The above will “do something useful” if the variable TESTVAR contains the pattern 123. What if I want to “do something” if TESTVAR does not contain 123? Well, until I figured it out, I was making an empty block for “do something”, and adding an else for the thing I really wanted. Ugly.

My first attempt was to change the “=~” to “!~” (seemed obvious). Nope, syntax error. I then tried “if !($TESTVAR =~ /123/)”. Nope, syntax error. I then tried “if (!$TESTVAR =~ /123/)”. No syntax error, but it doesn’t do what I wanted.

I stumbled on the solution via trial and error:

if (!($TESTVAR =~ /123/))

Ugh. The ! can only be applied to an expression, which is normally (but not always?!?) enclosed in parens. But, the if itself requires an expression, so you need to put parens around the negated expression as well. At least I know now…

The second problem was weighted matches. I was having the same problem. Once I put parens around my expressions, it started working. That’s one of the few places where the procmail syntax feels a drop cleaner:

COUNT=(/123/:b,1)

COUNT=$COUNT+(/456/:b,1)

COUNT=$COUNT+(/789/:b,1)

echo $COUNT

So, the above sets the variable COUNT to the number of times that the string 123 exists in the body of the message. That is then added to the number of times that the string 456 exists in the body, finally adding the number of times that the string 789 exists in the body. The total is then echoed to the console. Without the parens, no workie.

I don’t like the fact that I have to maintain the running count myself. In procmail, you basically set a limit and the tests stop once the limit is reached (which feels way more efficient). There might be a way to accomplish that with maildrop too, but I haven’t found it as yet…

While I fully expect to add more rules, or lines to existing rules, I can’t imagine a scenario where my file will even double from here, so it will end up at less than 1000 lines. That will be easier to maintain for a number of reasons, most notably syntax readability.

New Machine

On April 23rd I announced the christening of my new server. At the time, I put the percentage of services that had been ported over at 95. It’s been at least 5 days since I’ve been at 100%, so the new machine is definitely “official”. Everything has been updated to point to the new machine, and all but one thing are running as expected.

The only problem I have is with one VoIP provider. I can’t get any audio to work between us, and the problem is definitely on my end, which is the main reason for not naming the provider. I can still connect reliably to them from my old server, from a different server that I control, and from a softphone as well, so something is broken on my new server in the config for them. That said, all other providers work, including identically configured ones, so it’s not a firewall problem, nor generically a broken Asterisk install. I’m not happy with this, because I can’t think of anything more to test. I’ve written twice to the Asterisk mailing lists, with no useful suggestions left to try. :-(

I could probably write for hours on the experience of building the new machine. Very few people would maintain interest in that, I’m sure. I also don’t need it for a cathartic release, because I took very copious notes on the whole thing in a Google Notebook.

So, I’ll try to boil the essence down here, with the hope of not losing your interest too quickly. :-)

The purpose of the change was to upgrade the OS from Red Hat 9 to CentOS 5.0. That worked well. I actually installed CentOS 5.0 Beta first, and then did an upgrade through yum, which worked fine!

My first real disappointment was attempting to build OpenPKG on the new box. The concept sounded really cool to me. The biggest reason for moving from RH9 to CentOS5 was that newer RPMs were harder and harder to find for RH9. OpenPKG held out the promise that one wouldn’t have to worry about this in the future, with the added benefit that you would never accidentally step on the operating system’s packages.

Unfortunately, I ended up wasting a ton of time on it, and it eventually failed to install itself, claiming that gcc couldn’t create executables on the system. Of course it could, as I built quite a number of packages from source… So, great concept, just not right for me at this time…

Had a minor glitch with SELinux (first time I’ve been on a system that was running it). Had to temporarily disable some of the checks to get a package installed and running, but was able to turn it back on afterwards, and haven’t had a problem since.

I have been a very happy user of Courier-IMAP for years, and felt guilty about even considering an alternative (just a loyalty thing). But, I’d read a number of nice things about Dovecot, and it just went official 1.0 a few days before, so I decided to try it. I’m really happy with it. It worked correctly the first time, and configuration was as straightforward as I was led to believe. On the other hand, it wasn’t a quick config, because there are so many things that you can (and sometimes should) set. The single config file (which I like!) is huge, because it’s so well documented, that the choices are relatively simple. You just have to read all those darn docs… ;-)

Also installed the latest Postfix 2.4.0. I’ve been really happy with Postfix for years, and had little intention of switching that.

One minor nit about Linux in general. It’s a little annoying that dependencies can get out of whack quite easily. Some system thing depends on openssl-0.9.7 (for example), and you know that 0.9.8e fixes some bugs, and perhaps some new software you’re installing wants that. So, now it needs to go in it’s own directory (’cause you can’t mess with the system one), and then every package has to be told where to find the new one, etc. It all works, but it’s still a PITA.

Installed the latest WordPress (which of course meant MySQL and PHP, etc.). This time, the email config problem that I had on the old machine just disappeared (hooray!). I didn’t config it any differently, so who knows what was wrong before…

Installed the latest Zope (2.10.3, not Zope 3), and had remarkably few problems slurping up my old Data.fs file from a Zope 2.6.x installation. Very cool.

Switched from one webmail client to another, even though I had been happy with the former for years. The latter does more, of which I’m sure I won’t partake of the additional functionality anyway. It works, so that’s all I care about. I rarely use webmail, but when it’s necessary, it’s also ultra convenient (and, as stated, necessary). ;-)

One of the bigger odysseys was the installation of a Jabber server. This should probably be its own post, but if it was, I would never condense it, so I’ll do my best not to go on too much here. On the old machine, I was running jabberd-1.4.3 for years. Jabberd2 was just out at the time that I first installed 1.4.3 (they are not the same project). I was able to get jabberd2 to work at the time, but I could not get the AIM and ICQ transports to work, so I reverted to 1.4.3.

The jabberd14 project is still alive and kicking, and I could have saved a lot of headaches if I had stuck with it. But, for a while, I wanted to try ejabberd. It is the official server of jabber.org since February 2007, which seemed impressive to me. ;-)

Ejabberd is written in Erlang, and is supposed to scale like crazy (not that I have the slightest need for scale). The concept intrigued me. I’ll spare you all of the insane problems I had getting it to work right. Suffice it to say that it was not my fault, which is rare in these situations. ;-)

When I finally got it to work stably, I installed the Python-based AIM and ICQ transports (PyICQ-t and PyAIM-t). The AIM transport worked correctly, and the ICQ one was flaky (solution later on).

Then Rob Page asked me to take a look at Openfire (previously called Wildfire). It sounded cool, and since I was having a problem with the ICQ transport, I figured I’d give it a shot. Man, it installed so easily from RPM, didn’t touch a single file on the system, could be uninstalled trivially, etc. In summary, I liked it instantly. I wasn’t crazy about running a JVM on the system full time, but the load would be negligible, so I decided to switch to it. Of course, while it worked well, and the administration was wonderful, the ICQ plugin was experimental (the AIM one is production), and it behaved like an experimental plugin, which put me where the other one did. There were a few other small annoyances in Openfire as well.

That made me decide to go back and beat my head on the ejabberd server and transports. Long story short, after investigating my setup on the old machine (prompted by Z_God in the Python Transports conference room), I noticed that I didn’t understand how transports speak to the main server. I had them both speaking on the same port (which the sample config file showed!), but on the working server, each transport spoke to the server on its own port! I switched ICQ and AIM to speak to ejabberd on separate ports, and voila, it has been rock solid ever since. I have retired Openfire, and am a very happy ejabberd and python-transports customer! :-)

That’s pretty much it (at least at a high level). I’m happy with the machine. As usual, more twists and turns than one hopes for, but also more learning experiences than I expected, and interesting ones at that, mostly ending in success. Now if I can only figure out that one SIP provider audio problem, I could get back to some serious poker playing. ;-)