I've been noticing an escalation over the past several months in direct-to-MX spam from agents using total pipelining -- that is, blowing everything from HELO to . into their end of the socket as soon as they finish connecting, without waiting for any server responses. A side effect of this is that if the transaction is rejected after RCPT/MAIL, they feed the spam in anyway, triggering however many hundred error responses.
Here's a plugin which hooks the connect, and waits a moment (1s in this implementation) on STDIN before returning. If the connecting host waits, things proceed normally. If data shows up in that second, the remote host is jumping the gun. Qpsmtpd::SMTP can then hang up on the connect without troubling to send them responses they won't read anyway (patch attached). This is only lightly tested, but AFAIK it should be safe in principle. BTW, here's roughly how it plays out: Mar 12 21:25:43 atlantic qmail: 23763 loading plugins from /usr/local/qpsmtpd/plugins Mar 12 21:25:43 atlantic qmail: 23763 running plugin dnsbl Mar 12 21:25:43 atlantic qmail: 23763 running plugin check_earlytalker Mar 12 21:25:44 atlantic qmail: 23763 220 atlantic.devin.com ESMTP qpsmtpd 0.21-dev ready; send us your mail, but not your spam. Mar 12 21:25:46 atlantic qmail: 23763 dispatching HELO aol.com Mar 12 21:25:46 atlantic qmail: 23763 250 atlantic.devin.com Hi [81.202.216.233] [81.202.216.233]; I am so happy to meet you. Mar 12 21:25:52 atlantic qmail: 23763 dispatching MAIL FROM: <[EMAIL PROTECTED]> Mar 12 21:25:52 atlantic qmail: 23763 full from_parameter: FROM: <[EMAIL PROTECTED]> Mar 12 21:25:52 atlantic qmail: 23763 from email address : [<[EMAIL PROTECTED]>] Mar 12 21:25:52 atlantic qmail: 23763 running plugin require_resolvable_fromhost Mar 12 21:25:52 atlantic qmail: 23763 running plugin rhsbl Mar 12 21:25:52 atlantic qmail: 23763 rhsbl plugin: checking aol.com in dsn.rfc-ignorant.org Mar 12 21:25:52 atlantic qmail: 23763 running plugin check_badmailfrom Mar 12 21:25:52 atlantic qmail: 23763 getting mail from [EMAIL PROTECTED] Mar 12 21:25:52 atlantic qmail: 23763 250 [EMAIL PROTECTED], sender OK - how exciting to get mail from you! Mar 12 21:25:56 atlantic qmail: 23763 dispatching RCPT TO: <[EMAIL PROTECTED]> Mar 12 21:25:56 atlantic qmail: 23763 running plugin rhsbl Mar 12 21:25:56 atlantic qmail: 23763 running plugin dnsbl Mar 12 21:25:56 atlantic qmail: 23763 running plugin check_badmailfrom Mar 12 21:25:56 atlantic qmail: 23763 running plugin check_badrcptto Mar 12 21:25:56 atlantic qmail: 23763 550 mail to [EMAIL PROTECTED] not accepted here Mar 12 21:25:59 atlantic qmail: 23763 dispatching DATA Mar 12 21:25:59 atlantic qmail: 23763 503 RCPT first Mar 12 21:26:03 atlantic qmail: 23763 dispatching Message-ID: <[EMAIL PROTECTED]> Mar 12 21:26:03 atlantic qmail: 23763 500 Unrecognized command Mar 12 21:26:03 atlantic qmail: 23763 dispatching From: <[EMAIL PROTECTED]> Mar 12 21:26:03 atlantic qmail: 23763 500 Unrecognized command Mar 12 21:26:03 atlantic qmail: 23763 dispatching To: <[EMAIL PROTECTED]> Mar 12 21:26:03 atlantic qmail: 23763 500 Unrecognized command Mar 12 21:26:03 atlantic qmail: 23763 dispatching Subject: Find a Mortgage Loan... Refinance, 2nd, Purchase, Home Improvement 2556-4 Mar 12 21:26:03 atlantic qmail: 23763 500 Unrecognized command Mar 12 21:26:03 atlantic qmail: 23763 dispatching Date: Thu, 13 Mar 2003 08:23:47 -0100 [... etc, for several hundred lines of financing advice no distinguishing consumer should be without ] -- Devin \ aqua(at)devin.com, 1024D/E9ABFCD2; http://www.devin.com Carraway \ IRC: Requiem GCS/CC/L s-:--- !a !tv C++++$ ULB+++$ O+@ P L+++
# $Id$
#
# Hooks connect, checks to see if the remote host starts talking before
# we've issued a 2xx greeting. If so, we're likely looking at a direct-to-MX
# spam agent which pipelines its entire SMTP conversation, and will happily
# dump an entire spam into our mail log even if later tests deny acceptance.
use IO::Select;
sub register {
my ($self, $qp) = @_;
$self->register_hook('connect', 'connect_handler');
}
sub connect_handler {
my ($self, $transaction) = @_;
my $in = new IO::Select;
$in->add(\*STDIN) || return DECLINED;
if ($in->can_read(1)) {
$self->log(1, "remote host started talking before we said hello");
return DENY;
}
$self->log(10,"remote host said nothing spontaneous, proceeding");
return DECLINED;
}
Index: Qpsmtpd/SMTP.pm
===================================================================
RCS file: /cvs/public/qpsmtpd/lib/Qpsmtpd/SMTP.pm,v
retrieving revision 1.8
diff -u -r1.8 SMTP.pm
--- Qpsmtpd/SMTP.pm 6 Feb 2003 05:17:28 -0000 1.8
+++ Qpsmtpd/SMTP.pm 13 Mar 2003 08:16:38 -0000
@@ -67,15 +67,18 @@
return $self->respond(451, "Internal error - try again later - " . $msg);
}
-
sub start_conversation {
my $self = shift;
# this should maybe be called something else than "connect", see
# lib/Qpsmtpd/TcpServer.pm for more confusion.
my ($rc, $msg) = $self->run_hooks("connect");
- if ($rc != DONE) {
+ if ($rc == DENY) {
+ $self->respond(550, $msg);
+ return DENY;
+ } elsif ($rc != DONE) {
$self->respond(220, $self->config('me') ." ESMTP qpsmtpd "
. $self->version ." ready; send us your mail, but not your
spam.");
+ return DONE;
}
}
Index: Qpsmtpd/TcpServer.pm
===================================================================
RCS file: /cvs/public/qpsmtpd/lib/Qpsmtpd/TcpServer.pm,v
retrieving revision 1.5
diff -u -r1.5 TcpServer.pm
--- Qpsmtpd/TcpServer.pm 24 Sep 2002 10:56:35 -0000 1.5
+++ Qpsmtpd/TcpServer.pm 13 Mar 2003 08:16:38 -0000
@@ -1,5 +1,7 @@
package Qpsmtpd::TcpServer;
use Qpsmtpd::SMTP;
+use Qpsmtpd::Constants;
+
@ISA = qw(Qpsmtpd::SMTP);
use strict;
@@ -21,11 +23,13 @@
sub run {
my $self = shift;
+ my $rc;
# should be somewhere in Qpsmtpd.pm and not here...
$self->load_plugins;
- $self->start_conversation;
+ $rc = $self->start_conversation;
+ return if $rc != DONE;
# this should really be the loop and read_input should just get one line; I think
pgp00000.pgp
Description: PGP signature
