2011-12-10

Sniffles, libunaccept pt. 2

Ewelina and I have colds. We are missing parties and fun as a result, but instead I am being somewhat productive on PageKite and such things.

...

After the first 24 hours of my spam filtering experiments, I have 62 spam messages in my spam folder.

This is probably a bit less than I would have using the AVES system, but the downside is none of them are flagged as spam and if I weren't using GMail to read my klaki.net e-mail, they would all be sitting right there in my inbox.

I am going to evaluate the current policy for another 24 hours, and then disable libunaccept entirely for a final 24 to get a feel for how much of a difference it makes.

libunaccept

So, what's this libunaccept thing then? (Update: more here)

It is probably best described as a minimal user-space firewall written in C, which allows DNS-based policies and can be enabled for any application without the need to recompile.

How it works

On Unix, the standard function for receiving an incoming TCP/IP connection is called accept, and it is used by the vast majority of the servers written for the Internet - e-mail servers included.

What libunaccept does, is it wraps some filtering logic around the accept function. This logic consults a file named /etc/libunaccept.rc to decide if the connection is to be permitted or not. Rules can be based on IP networks or DNS names.

A minimal /etc/libunaccept.rc file might contain the following lines:

allow        127.0.0.0    255.0.0.0
allow:host   klaki.net
tarpit:host  spammer.com
deny         0.0.0.0      0.0.0.0

These lines are processed in order and processing stops when something matches. So:

  1. first, allow all connections from the local network,
  2. second, allow all connections from machines with DNS names containing the string klaki.net,
  3. third, be nasty to connections from spammer.com and leave them hanging without passing them on to the calling application,
  4. and finally, reject all other connections.

Every time a new connection is received libunaccept checks if its configuration file has been modified, to make sure it is always using the latest rules.

Using libunaccept as a spam blocker

Activating libunaccept for Klaki's mailserver was very simple, if a bit hackish. First I created a shell script in /usr/local/sbin with the following contents:

#!/bin/bash
export LD_PRELOAD=/usr/local/lib/libunaccept.so
exec /usr/sbin/sendmail "$@"

Then I edited the /etc/init.d/sendmail script (yes, Klaki still uses sendmail, hawk, spit) to invoke that script instead of the standard binary.

That was enough to add libunaccept support to sendmail. 1)

The next step was to create two policy files:

  1. /etc/libunaccept-nodirect.rc allows only white-listed connections.
  2. /etc/libunaccept-nodialup.rc allows all connections except those from hosts with suspicious names or IPs.

Finally, I added the following lines to /etc/crontab:

# Rotate SMTP access policy a few times day
00 07 * * * root cp -f /etc/libunaccept-nodialup.rc /etc/libunaccept.rc
00 12 * * * root cp -f /dev/null /etc/libunaccept.rc
00 18 * * * root cp -f /etc/libunaccept-nodialup.rc /etc/libunaccept.rc
00 00 * * * root cp -f /etc/libunaccept-nodirect.rc /etc/libunaccept.rc

Combined, those steps implement the time-of-day based spam policy I described in the previous post. Not too hard.

At the moment changes to the policy are made by manually editing the files in /etc/, but the file format is so simple that it would be trivial to write log parsers which update white- or black-list fragments automatically and activate those at different times of day instead of or in addition to the hand-crafted policies.

What about iptables or libwrap?

So... why did I reinvent this particular wheel? Wouldn't it have been possible to use the kernel's firewall support or some more traditional tools? I could be wrong, but I think the answer was "not quite". Also, it was a rather fun project with lots of "hack value".

The features libunaccept has that iptables lacks are:

  1. It can make decisions based on DNS names.
  2. It is easy to allow an unprivileged process to edit the config file, which allows easy policy scheduling or automated white/black-listing.
  3. Tarpitting.

The features libunaccept has that libwrap (/etc/hosts.allow and /etc/hosts.deny) lacks are:

  1. The application does not need to be recompiled, you can even wrap a Python or Perl program with LD_PRELOAD.
  2. Tarpitting.

It would probably have been better to use something based on libwrap rather than roll my own rules language and parser, but I was looking for something as minimal as possible and I am not sure how easy it is (or if it is even possible) to include external library code in something meant for use with LD_PRELOAD.

If I decide that libunaccept is a project worth spending more time on, I may look into libwrap integration again at some point.

Either way, I will probably write some docs and put it on github. It's a shame to let the code rot unused on my hard-drive, at the very least it is a nice working example of how to use LD_PRELOAD.


1) A cleaner way to do this would have been to create a new init script with the LD_PRELOAD line included, and disable the old sendmail startup script. I will probably switch to that method after publishing this post.

Tags: life, tech


Recent posts

...