[Greylist-users] qmail implementation (fwd)

Tony Arcieri tarcieri at atmos.colostate.edu
Tue Jun 24 16:42:53 PDT 2003

I asked the users of the qmail list if they were working on graylisting,
and I received this reply (complete with a qmail patch).

It should be trivial to rewrite the "qmail-checkenv" program to use
whatever backend you want for maintaining the list itself.

If anyone wants to work on developing this into a full-out SQL-backed
graylisting solution for qmail let me know.

Tony Arcieri

---------- Forwarded message ----------
Date: Mon, 23 Jun 2003 21:50:09 -0400 (EDT)
From: Kelly French <kfrench at federalhill.net>
Subject: Re: Graylisting

I was hesitating on posting this for fear of getting flamed, but here it
is anyhow.  I'd love to get comments, but I'll put on my asbestos
underwear before I read any of them.

I read the same url and thought it was interesting.  So I created the
following patch.  When the CHECKENV enviornment variable is set,
/var/qmail/bin/qmail-checkenv is executed.  checkenv stands for check
envelope, not check enviornment.  The first argument is the envelope
sender, the second arg is the envelope recipient.  qmail-checkenv is
called for _each_ RCPT TO.  If someone sends you a single email with 200
recipients, qmail-checkenv gets called 200 times (flame away!).  If
qmail-checkenv returns 0, then then envelope is good.  If it returns 100,
then it's a permanent failure.  Any other return value (preferably 111)
signifies a temporary error.

This lets you block inbound email send to specific addresses during the
SMTP conversation (as opposed to bouncing them after the fact), or even
implement the graylisting.  You can even block recipient addresses that
have %'s in them to get those pesky relay-testers to shut up.  Don't
forget that TCPREMOTEIP is already set by tcpserver, so the third argument
wasn't needed.

I created a shell script that does poor-man's graylisting.  It doesn't do
everything that the suggested solution does, but it gets you some of the
way there.  I don't suggest you actually use the script or patch.  They
have had minimal testing.

The patch is inline, the script is attached.

diff -u qmail-1.03/qmail-smtpd.c qmail-1.03-checkenv/qmail-smtpd.c
--- qmail-1.03/qmail-smtpd.c	1998-06-15 06:53:16.000000000 -0400
+++ qmail-1.03-checkenv/qmail-smtpd.c	2003-06-21 15:58:21.000000000 -0400
@@ -19,6 +19,8 @@
 #include "env.h"
 #include "now.h"
 #include "exit.h"
+#include "fork.h"
+#include "wait.h"
 #include "rcpthosts.h"
 #include "timeoutread.h"
 #include "timeoutwrite.h"
@@ -45,10 +47,15 @@
 void die_read() { _exit(1); }
 void die_alarm() { out("451 timeout (#4.4.2)\r\n"); flush(); _exit(1); }
 void die_nomem() { out("421 out of memory (#4.3.0)\r\n"); flush(); _exit(1); }
+void die_tempfail() { out("421 temporary envelope failure (#4.3.0)\r\n"); flush(); _exit(1); }
+void die_fork() { out("421 Unable to fork (#4.3.0)\r\n"); flush(); _exit(1); }
+void die_exec() { out("421 Unable to exec (#4.3.0)\r\n"); flush(); _exit(1); }
+void die_childcrashed() { out("421 Aack, child crached. (#4.3.0)\r\n"); flush(); _exit(1); }
 void die_control() { out("421 unable to read controls (#4.3.0)\r\n"); flush(); _exit(1); }
 void die_ipme() { out("421 unable to figure out my IP addresses (#4.3.0)\r\n"); flush(); _exit(1); }
 void straynewline() { out("451 See http://pobox.com/~djb/docs/smtplf.html.\r\n"); flush(); _exit(1); }

+void err_permfail() { out("553 permanent envelope failure (#5.7.1)\r\n"); flush(); _exit(1); }
 void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }
 void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
 void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); }
@@ -222,6 +229,29 @@
 stralloc mailfrom = {0};
 stralloc rcptto = {0};

+void checkenv()
+  int child;
+  int wstat;
+  char *checkenvarg[] = { "bin/qmail-checkenv", mailfrom.s, addr.s, 0 };
+  switch(child = fork())
+  {
+    case -1:
+      die_fork();
+    case 0:
+      execv(*checkenvarg,checkenvarg);
+      die_exec();
+  }
+  wait_pid(&wstat,child);
+  if (wait_crashed(wstat))
+    die_childcrashed();
+  if (wait_exitcode(wstat) == 0) return;
+  if (wait_exitcode(wstat) == 100) err_permfail();
+  die_tempfail();
 void smtp_helo(arg) char *arg;
   smtp_greet("250 "); out("\r\n");
@@ -261,6 +291,7 @@
   if (!stralloc_cats(&rcptto,"T")) die_nomem();
   if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
   if (!stralloc_0(&rcptto)) die_nomem();
+  if (env_get("CHECKENV")) checkenv();
   out("250 ok\r\n");

-------------- next part --------------

# This is a poor-man's implementation of Greylisting

# 5 minute delay instead of the suggested 1 hour

# Lowercase everything
sender=`echo "${1}" | /usr/bin/tr [:upper:] [:lower:]`
recipient=`echo "${2}" | /usr/bin/tr [:upper:] [:lower:]`

# Create one directory for each IP that sends us email
if [ ! -d "tempfail/${TCPREMOTEIP}" ]; then
	/bin/rm -f "tempfail/${TCPREMOTEIP}"
	/bin/mkdir "tempfail/${TCPREMOTEIP}"


# If we haven't seen this triple yet, fail with 421
if [ ! -e "$tempfail" ]; then
	/usr/bin/logger "First receipt from $tempfail"
	date >"$tempfail"
	exit 1 
	# Do this to update the access time on the file
	cat "$tempfail" >/dev/null
	filedate=`/bin/ls -l --time-style "+%s" "$tempfail" | /usr/bin/awk '{print $6}'`
	now=`date "+%s"`
	diff=$[$now - $filedate]
	if [ ${diff} -le ${TEMPFAILDELAY} ]; then
		/usr/bin/logger "Blocked early attempt from $tempfail"
		exit 1

# If we get this far, we're good to go
exit 0

More information about the Greylist-users mailing list