--- category: '' date: 2006/04/01 13:19 description: '' link: '' priority: '' slug: '37' tags: programming, qmail title: Fighting Spam with Qmail (part III) type: text updated: 2006/04/01 13:19 url_type: '' --- .. raw:: html

Yes, after over two years, Fighting spam with Qmail is back.

You can see part 1 and part 2 of this series too, if you want.

This installment will be about using RBL effectively to decrease your spam-related server load, and the spam your users get.

The software described here is now part of my plugins package

What is RBL?

RBL is a nice idea, using DNS to tag IPs.

For example, suppose you want to tag all the IPs that you know send spam:

    10.0.0.14
    172.16.2.3
    

You could have a DNS server for the domain "myrbl.tld", and then resolve for those IPs like this:

    10.0.0.14.myrbl.tld -> 127.0.0.1
    172.16.2.3.myrbl.tld -> 127.0.0.2
    

You can codify your opinion of that IP in the answer. For instance, 127.0.0.1 may mean 10.0.0.14 is an open relay, and 127.0.0.2 may mean that 172.16.2.3 is a known spammer.

Or you can just say "host not found" is good, anything else is bad.

There are a number of RBL servers out there, but I am most familiar with spamcop.net and njabl.org

So, whenever you get a SMTP connection, you can check the IP agains one database (or both), and then you can decide to block (or not) the connection based on the answer you get.

Neat, right?

The problems with RBL

There are many issues you need to keep in mind. Most of them are policy related, a few are technical, but here are some I came up with:

You can be creative here, people, as long as you read this thing all the way through :-)

Tools

Qmail comes with a RBL tool, called rblsmtpd . Personally I don't like it much, and I am not going to cover it further, but here's what I understand about how it's meant to be used:

You hook it to your qmail-smtpd command chain (before qmail-smtpd), and then if TCPREMOTEIP is RBL-listed, it hangs up the connection.

My problem with that is that if you want to use a dynamip-ip address RBL, then almost none of your own users will be able to use your own server!

A solution to that is to also run a MSA server on port 587, without RBL checks, with mandatory SMTP AUTH, and tell your users to use that.

I do recommend running such a MSA server. However, the support load for the change to MSA is large (all common MUAs come preconfigured for port 25), and users will make mistakes, then get a cryptic error about how their connection is not wanted.

I prefer to use a plugin along with Qmail-SPP.

Qmail-SPP is a qmail patch that lets you hook your own plugins (small programs) into qmail-smtpd at every step of the process, make decisions about it, and modify headers (and other things, it's really cool functionality!)

What I do is put a plugin I wrote, called rblchecks at the beginning of the MAIL command.

If the user is authenticated, it will do nothing, so it will accept mail from your own users.

But if the user is not authenticated, and the source IP is RBL-listed, it will kick you out (actually, it only gives you a message).

I wrote it using the Better String Library mainly because programming C for arbitrary input I am a complete chicken.

But first, a few conventions on how you write a qmail-spp plugin.

  1. Everything you print on stdout goes to qmail-spp, and it's commands. Like "Give this error" or "Add this header"
  2. You get your information about the message and the connection from environment variables. for example TCPREMOTEIP is the remote ip.
  3. What you print on stderr goes to the logs. So be careful, and log meaningfully! Specifically, I always print the plugin name, and the parent process ID, which can be used to group all messages from a single connection, thus making the log useful.
  4. Be careful.
  5. Be quick. Whatever you delay, there may be a guy waiting, looking at outlook while you dither.

To make my plugin more configurable, I decided that you could pass a colon-separated list of RBL servers to be checked in the RBLSERVERS environment variable.

Usually, you would set this either on qmail's startup script, or, if you are using tcpserver (you probably are if you are using qmail), from its tcprules control file.

For example, this is a perfectly legal and nice rules file:

    :allow,RBLSERVERS="bl.spamcop.net:combined.njabl.org",QMAILQUEUE="/usr/bin/simscan"
    

Yup. One line. No relays (use authentication, guys!). Two RBL lists. Simscan so you get no viruses.

Then, you configure the rblchecks plugin in your smtpplugins file on the mail section.

And that's it. You should see your spam go down, and you should see your server load go down. Instantly.

So, you want the plugin code? Get it then!

As a bonus, you get a whole collection of plugins for different purposes, but please, please, please, don't use them unless you really know what you are doing. This is pretty much all new code :-)

Any comments, bug reports, happy stories... email me! Or leave comments. Or both!

And here is the code for this plugin (there may be newer code in the latest plugin):

/*
* Copyright (C) 2003-2004 Perolo Silantico <per.sil@gmx.it>
* Copyright (C) 2006 Roberto Alsina <ralsina@kde.org>
*
* For any questions please contact Roberto Alsina, because
* this version is heavily modified from the original.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/


\#include <arpa/inet.h>
\#include <netdb.h>
\#include <stdio.h>
\#include <stdlib.h>
\#include "bstrlib.h"
\#include <sys/types.h>
\#include <unistd.h>

int
check_rbl (bstring lookup_addr, const char *rbl)
{
  
struct addrinfo *ai = NULL;

  
bstring lookupname = bformat ("%s.%s", lookup_addr->data, rbl);
  
if (getaddrinfo (lookupname->data, NULL, NULL, &ai))
    
{
      
if (ai)
        
freeaddrinfo (ai);
      
return 0;
    
}
  
freeaddrinfo (ai);
  
return 1;
}

bstring
envtostr (char *vname)
{
  
bstring retval;
  
retval = bfromcstr (getenv (vname));
  
if (!retval)
    
retval = bfromcstr ("");

  
return retval;
}


int
main (int argc, char *argv[])
{
  
bstring ip = bfromcstr (getenv ("TCPREMOTEIP"));
  
unsigned long address;
  
int ppid = getppid ();

  
if (!ip)                      //Somehow we are running in a bad situation
    
{
      
printf ("D\n");
      
fprintf (stderr, "rblchecks: pid %d - no TCPREMOTEIP\n", ppid);
      
exit (0);
    
}



  
address = inet_addr (ip->data);
  
bstring addr = bformat ("%lu.%lu.%lu.%lu",
                          
(address & 0xff000000) >> 24,
                          
(address & 0x00ff0000) >> 16,
                          
(address & 0x0000ff00) >> 8,
                          
(address & 0x000000ff) >> 0);

  
//If authenticated, don't check at all
  
if (getenv ("SMTPAUTHUSER"))
    
{
      
printf ("A\n");
      
fprintf (stderr,
               
"rblchecks: pid %d - No checks performed, because user is authenticated\n",
               
ppid);
      
exit (0);
    
}

  
bstring rbl = bfromcstr (getenv ("RBLSERVERS"));
  
if (!rbl)
    
rbl = bfromcstr ("bl.spamcop.net");

  
struct bstrList *list = bsplit (rbl, ':');

  
int i = 0;

  
for (; i < list->qty; i++)
    
{
      
bstring serv = list->entry[i];
      
if (serv->slen == 0)
        
continue;
      
if (check_rbl (addr, serv->data))
        
{
          
printf
            
("E541 Your IP (%s) is blocked, more information at http://%s\n",
             
ip->data, serv->data);
          
fprintf (stderr, "rblchecks: pid %d - 541 Blocked by %s (%s)\n",
                   
ppid, serv->data, ip->data);
          
exit (0);
        
}
    
}

  
// No RBL issues
  
fprintf (stderr, "rblchecks: pid %d - Accepted %s\n", ppid, ip->data);
  
exit (0);
}