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
 

Attachment: pgp00000.pgp
Description: PGP signature

Reply via email to