--- category: '' date: 2003/09/24 20:36 description: '' link: '' priority: '' slug: '11' tags: qmail title: Fighting Spam with Qmail (part II) type: text updated: 2003/09/24 20:36 url_type: '' --- .. raw:: html

Introduction

In part I of this series, I explained how to filter spam after qmail takes it, but before the user sees it. While that achieves the important goal of avoiding user annoyance, it still leaves one issue open: Spam is using resources in the qmail server. How?

SMTP connections:
Spammers are opening connections to your SMTP server, thus increasing the number of SMTP processes you run.
Local deliveries:
If spam reaches the ifspamh filter we installed before, it already has been delievered locally and if it is spam, it will have to be delivered again, into one of the spam folders.
Bounces:
Qmail doesn't verify that local recipients are valid before accepting a message. This is a serious problem. For example, if your server handles the mydomain.com domain, and qmail receives a messages for a user joe@mydomain.com who doesn't exist, it is accepted, queued, attempted to deliver locally (which finally gives an error), then a bounce is created, and the bounce is attempted to deliver remotely. If the original message was spam, that bounce will probably bounce as well, causing a second delivery (maybe local, maybe remote, depending on your qmail's doublebounceto and doublebouncehost configuration files)

This is particularly gruesome if a spammer decides to harvest addresses by posting to all letter combinations. In that case, qmail may have to handle thousands (or tens of thousands) of extra deliveries!

In this article, I will explain how to solve, at least partly, these problems, using a replacement for qmail's SMTP server, called qpsmtpd. All commands given are as I used in a Red Hat 8.0 system. I doubt it, but some may need to be changed in other distros, so check the docs of each command if you don't understand what a command does. Remember, you don't know me, I may be evil or dumb!

I also assume that you already have a working, operational qmail setup. If your qmail is not working, following this tutorial will only break it further, so stop right now and fix it!

Note: some of this stuff only works if you use a qmail setup with regular user accounts, a fully virtual setup like vmailmgr. is NOT going to work right in some places.

Implementation

Installing qpsmtpd

First, get qpsmtpd. I am using version 0.26, the latest right now.

Check out the README. This is weird, but to read it right, you have to use the command perldoc README . The rest of this section is pretty much covered in the README, so you should just do what it says, unless you don't understand it, or if I explain why it doesn't work, then you can do what this page says ;-)

Perl Modules

Qpsmtpd requires a couple of perl modules that you may not have installed, Net::DNS and Mail::Address .

If your perl is older than 5.8.0, you also need Data::Dumper and File::Temp (mine was 5.8.0, so I already had them).

The README says that the easiest way to get them is the CPAN shell. That is true, but it has a serious problem: if your perl is installed by the distribution (not self-compiled) and you want to use an update tool, like apt-get or up2date, then these modules will get out of sync the next time your perl is updated, and qpsmtpd will stop working! So, if you possibly can, get the modules in a packaged form. Updating your system is difficult enough already ;-).

Net::DNS already comes with the distribution I used (RH 8.0), so I just installed the RPM from freshrpms.net.

Mail::Address I got from Dag Wieers repository. Be careful to see that the module does work (it worked flawlessly for me, though). If later on you see any funny stuff, just remove the rpm and go the CPAN route.

That way, when I update perl in the future, I will either get a warning that I am breaking these modules dependencies, or update them automatically as well.

For better performance, (remember we are replacing a tight C program with a Perl script!) it's recommended you use PPerl.

I can't find a decent RPM of PPerl, so just use CPAN for this one:

    [root@localhost root]# perl -MCPAN -e shell
    cpan shell -- CPAN exploration and modules installation (v1.61)
    ReadLine support available (try 'install Bundle::CPAN')
    
    cpan> install PPerl
    :
    : (time passes)
    :
    PPerl is up to date.
    

To make qpsmtpd use PPerl instead of Perl, edit ~smtpd/qpsmtpd/qpsmtpd and make the first line say #!/usr/bin/pperl -Tw One minor annoyance of using pperl is that when you bring down the service and then raise it again, the qpsmtpd processes may still be the same ones you had before stopping. So, make sure you kill all qpsmtpd instances after stopping the service, or some configuration changes may not be in effect.

Installing qpsmtpd

Add a user called smtpd, and expand qpsmtpd there:

    adduser smtpd -s /sbin/nologin -m
    cd ~smtpd
    wget http://develooper.com/code/qpsmtpd/files/qpsmtpd-0.26.tar.gz
    tar xzvf qpsmtpd-0.26.tar.gz
    mv qpsmtpd-0.26 qpsmtpd
    
Running qpsmtpd

Put the IP address where qpsmtpd will listen for connections in ~smtpd/qpsmtpd/config/IP (usually just 0, and listen in all addresses).

The smtpd user needs write access to ~smtpd/tmp/ but no other directory, so do something like this:

    chown -R root.smtpd ~smtpd
    find -t d ~smtpd -exec chmod -R 750 {} \;
    mkdir ~smtpd/tmp
    chown -R smtpd ~smtpd/tmp
    chmod -R g+rx ~smtpd
    chmod 700 ~smtpd/tmp
    

I use the supervise tools to run my qmail, so qpsmtpd can be managed by them like this:

    svc-add ~smtpd/qpsmtpd/
    

I had to modify qpsmtpd's run script /service/qpsmtpd/run because it used /usr/local/bin/tcpserver and in my system it's /usr/bin/tcpserver instead. I also had to change it so that it would use the relaying rules for smtpd (-x option). The end result is like this:

    #!/bin/sh
QMAILDUID=`id -u smtpd` NOFILESGID=`id -g smtpd` exec /usr/local/bin/softlimit -m 25000000 \ /usr/bin/tcpserver -c 10 -v -R -p \ -u \$QMAILDUID -g \$NOFILESGID -x /etc/tcpcontrol/smtp.cdb `head -1 config/IP` smtp \ ./qpsmtpd 2>&1

If you run your qmail some other way, just install supervise anyway (it's easier than figuring how to run qpsmtpd without it ;-)

By default qpsmtpd should work as a drop-in replacement for qmail's smtpd. so all you have to do now is stop smtpd and start qpsmtpd. Using the supervise scripts, it's something like this:

    [root@localhost root]# svc-stop smtpd
    Stopping smtpd: smtpd svcsmtpd/log done.
    [root@localhost root]# svc-start qpsmtpd
    Starting qpsmtpd: qpsmtpd/log qpsmtpd done.
    

If qpsmtpd started right, you should see something like this:

    [root@localhost root]# telnet localhost 25
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    220 localhost.localdomain ESMTP qpsmtpd 0.26 ready; send us your mail, but not your spam.
    

If it didn't work, qpsmtpd's log is at /service/qpsmtpd/log/main/current , read it carefully!

Configuring qpsmtpd

Of course, a slower drop-in replacement for smtpd is not what we wanted ;-). What makes qpsmtpd worth installing is its plugins. Here are some I found useful:

check_badmailfrom
Support for qmail's badmailfrom file. Read the qmail-smtpd docs for explanation.
check_badrcptto
Like badmailfrom but for recipient addresses, the doc says.
check_earlytalker
Some spammers will open a connection, and without waiting for the greeting, will just dump the mails through the connection. They do it to work faster. Well, no sane, honest mailer does that, so, why not reject the mail if the sender tries to talk too early? That's what this plugin detects.
check_relay
Allow relaying if the RELAYCLIENT variable is set. This is the usual mechanism used in qmail to allow relaying, so you are very likely to need this! (unless you use check_rcptto)
count_unrecognized_commands
This one closes the connection if the sender makes mistakes. Why this is necessary is a bit long to explain, though.
dnsbl and rhsbl
These use public lists of hosts that send spam. I am not a fan of using that kind of thing, but if you are, they are here.
spamassassin
Enable spamassassin server-wide. I prefer to do it per-user, but hey, whatever floats your boat.
check_rcptto
This one is very important... and it's not included in qpsmtpd by default! You can get it here, I got it from somewhere in qpsmtpd's mailing list archives, posted by its author, Andrew Pam, just save it in ~smtpd/qpsmtpd/plugins .It should be used instead of check_relay.

Keep in mind that this plugin only works if you use a qmail setup with regular user accounts, a fully virtual setup like vmailmgr.

The plugins are enabled in the ~smtpd/qpsmtpd/config/plugins file, here's mine:

    #
    #  Example configuration file for plugins
    #
    
    # enable this to get configuration via http; see perldoc
    # plugins/http_config for details.
    #   http_config http://localhost/~smtpd/config/  http://www.example.com/smtp.pl?config=
    
    #quit_fortune
    
    check_earlytalker
    count_unrecognized_commands 4
    
    #require_resolvable_fromhost
    
    #rhsbl
    #dnsbl
    check_badmailfrom
    check_badrcptto
    check_spamhelo
    
    # this plugin needs to run after all other "rcpt" plugins
    #check_relay
    #Using check_rcptto instead of check_relay
    check_rcptto
    
    
    # content filters
    #klez_filter
    
    # You can run the spamassassin plugin with options.  See perldoc
    # plugins/spamassassin for details.
    #
    #spamassassin
    
    # rejects mails with a SA score higher than 20 and munges the subject
    # of the score is higher than 10.
    #
    #   spamassassin reject_threshold 20 munge_subject_threshold 10
    
    
    # run the clamav virus checking plugin
    # clamav
    
    # queue the mail with qmail-queue
    queue/qmail-queue
    
    

If all worked right, you should get an error message when you send an email to an unknown local user, and it should work for those who exist:

    [root@localhost plugins]# telnet localhost 25
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    220 localhost.localdomain ESMTP qpsmtpd 0.26 ready; send us your mail, but not your spam.
    helo pepe
    250 localhost.localdomain Hi [127.0.0.1] [127.0.0.1]; I am so happy to meet you.
    mail from: ralsina@whatever.org
    250 ralsina@whatever.org, sender OK - how exciting to get mail from you!
    rcpt to: ralsina@mydomain.com
    250 ralsina@mydomain.com, recipient ok
    rcpt to: noone@mydomain.com
    550 Return to sender, address unknown
    

Final Words

While installing and configuring qpsmtpd is not a trivial effort, the end result is a smtp server that uses less network resources, perhaps at the cost of a higher CPU or memory usage. A client of mine handles each day many thousands of messages that would be bounced more efficiently by qpsmtpd's check_rcptto.

For small servers, this is maybe not worth the effort. For larger ones, it's almost mandatory. I keep wishing someone will patch the regular qmail-smtpd so it will do something like check_rcptto, but haven't found it yet.

There's a replacement called magic-smtpd that I haven't really tried yet, and may be the subject of a future article.

I still haven't figured out how to use some other techniques like tarpitting and rate limiting with qpsmtpd. As soon as I do, there will be a "Fighting Spam with Qmail (part III)"

Important Links