Yes, after over two years, Fighting spam with Qmail is back.
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
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:
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.
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.
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:
What RBL should you use?
RBLs are not all equal. Some are more zealous than others, some would simply make your server unusable. Some of them, once you get in, it's almost impossible to get out, and they list routinely many legitimate servers. I am not vouching even for the ones I use!
What is nore important, spam or email?
Whatever you use, there is bound to be some email that can not be sent to you easily. Some friend/client/whatever of yours is going to have his SMTP server listed and it's going to be annoying.
So, if you must absolutely, positively get all your mail, RBLs are not a great idea.
You will eventually have to deal with extra support questions, complaints, etc. about why you are "blocking someone's mail" and somesuch. If you are careful, that should be kept to a minimum. But never to zero.
When do you perform the check? What do you do with the checks results? Here are some examples:
- Check on connection, and refuse connections from listed IPs.
- Check on the MAIL command, and refuse to accept mails from listed IPs if the user is not authenticated.
- Check on MAIL and add a header to all mail coming from RBL-listed servers with nonauthenticated and let it pass, later file it in a separate folder.
You can be creative here, people, as long as you read this thing all the way through :-)
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.
- Everything you print on stdout goes to qmail-spp, and it's commands. Like "Give this error" or "Add this header"
- You get your information about the message and the connection from environment variables. for example TCPREMOTEIP is the remote ip.
- 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.
- Be careful.
- 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.
For example, this is a perfectly legal and nice rules file:
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 <email@example.com>
* Copyright (C) 2006 Roberto Alsina <firstname.lastname@example.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
* 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.
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))
envtostr (char *vname)
retval = bfromcstr (getenv (vname));
retval = bfromcstr ("");
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
fprintf (stderr, "rblchecks: pid %d - no TCPREMOTEIP\n", ppid);
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"))
"rblchecks: pid %d - No checks performed, because user is authenticated\n",
bstring rbl = bfromcstr (getenv ("RBLSERVERS"));
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)
if (check_rbl (addr, serv->data))
("E541 Your IP (%s) is blocked, more information at http://%s\n",
fprintf (stderr, "rblchecks: pid %d - 541 Blocked by %s (%s)\n",
ppid, serv->data, ip->data);
// No RBL issues
fprintf (stderr, "rblchecks: pid %d - Accepted %s\n", ppid, ip->data);