Introducing zombies and a reaper.

24184 Accepted connection 0/15 from 64.185.226.20 / mx.<snip>olnews.com
24184 Connection from mx.<snip>olnews.com [64.185.226.20]
24184 (connect) ident::geoip: US, United States
24184 (connect) ident::p0f: Windows 7 or 8
24184 (connect) karma: fail, ZOMBIFIED, 1 naughty, 0 nice, 8 connects
24184 (connect) relay: skip: no match
24184 (connect) dnsbl: skip, zombie
24184 (connect) earlytalker: skip, zombie
24184 220 mail.theartfarm.com ESMTP qpsmtpd 0.84 ready; send us your mail, but 
not your spam.
24184 dispatching EHLO mx.<snip>olnews.com
24184 (ehlo) helo: skip, zombie
24184 250-mail.theartfarm.com Hi mx.<snip>olnews.com [64.185.226.20]
24184 250-PIPELINING
24184 250-8BITMIME
24184 250-SIZE 25000000
24184 250 STARTTLS
24184 dispatching MAIL FROM:<DIY_Energy@<snip>olnews.com> BODY=8BITMIME
24184 (mail) resolvable_fromhost: skip, zombie
24184 (mail) sender_permitted_from: skip, zombie
24184 250 <DIY_Energy@<snip>olnews.com>, sender OK - how exciting to get mail 
from you!
24184 dispatching RCPT TO:<user@example>
24184 (rcpt) reaper: disconnecting zombie
24184 550 You were naughty. You are penalized for 0.98 more days.
24184 click, disconnecting
24184 (post-connection) connection_time: 0.102 s.
13489 cleaning up after 24184

Notice the bolded lines.  Instead of rejecting the message early, plugins like 
dnsbl and karma can zombify it. To increase efficiency, other plugins detect 
the zombie state and skip processing.  As you can see from the connection time, 
the transaction zipped through to completion with no appreciable delay.

The zombified connection was harvested by my companion plugin, reaper. It's a 
very simple plugin. I'll list the code before the POD, because its so short:

sub register {
    my ($self, $qp ) = shift, shift;
    $self->log(LOGERROR, "Bad arguments") if @_ % 2;
    $self->{_args} = { @_ };
    $self->{_args}{reject} ||= 'rcpt';
    $self->{_args}{reject_type} ||= 'disconnect';

    my @hooks  = qw/ connect mail rcpt data data_post /;
    my %hooks  = map { $_ => 1 } @hooks;
    my $reject = lc $self->{_args}{reject};

    if ( ! $hooks{$reject} ) {
        $self->log( LOGERROR, "invalid hook $reject" );
    };

    foreach my $hook ( @hooks ) {
        next if $hook ne $reject;
        $self->register_hook($hook, 'reaper');
    };
}

sub reaper {
    my $self = shift;
    my $zombie = $self->connection->notes('zombie') or do {
        $self->log(LOGINFO, "alive");
        return DECLINED;
    };
    $self->log(LOGINFO, "disconnecting zombie");
    return ( $self->get_reject_type(), $zombie );
};





NAME
    reaper - dispose of zombie connections

BACKGROUND
    Rather than immediately terminating naughty connections, plugins often
    mark the connections and dispose of them later. Examples are dnsbl,
    karma, greylisting, require_resolvable_fromhost and SPF.

    This practice is based on RFC standards and the belief that malware will
    retry less if we disconnect after RCPT. This may have been true, and may
    still be, but my observations in 2012 suggest it makes no measurable
    difference whether I disconnect during connect or rcpt. YMMV.

    Disconnecting later is inefficient because other plugins continue to do
    their work, oblivious to the fact that the connection is destined for
    the bit bucket.

DESCRIPTION
    Reaper provides the following:

  efficiency
    Reaper provides a way to tell other plugins that a connection is undead.
    For efficiency, most plugins should skip processing undead connections.
    Plugins like SpamAssassin and DSPAM can benefit from using these undead
    connections to train their filters.

    I believe disconnecting 80% of connections after one DNS query (dnsbl or
    one DB query (karma) and 0.01s of compute time is an easy win. Even if
    it did increase the number of connections.

  zombie cleanup
    Instead of each plugin having to mop up the undead, reaper does it. Set
    *reject* to the hook you prefer to reject in and reaper will reject all
    zombie connections, regardless of who zombified them, exactly when you
    choose.

  simplicity
    Rather than having plugins split processing across hooks, they can run
    to completion when they have the information they need, issue a *reject
    zombie* if warranted, and be done.

    This may help reduce the code divergence between the sync and async
    deployment models.

  zombies, aka undead
    <reaper> provides a a consistent way for plugins to mark connections as
    zombies or undead. Set the connection note *zombie* to the message you
    wish to send the naughty sender when reaper rejects them.

       $self->connection->notes('zombie', $message);

    This happens for plugins automatically if they use the
    $self->get_reject() method and have set *reject zombie* in the plugin
    configuration.

CONFIGURATION
  reject
      karma reject [ connect | mail | rcpt | data | data_post ]

  reject_type [ temp | perm | disconnect ]
    What type of rejection should be sent? See docs/config.pod

  loglevel
    Adjust the quantity of logging for this plugin. See docs/logging.pod

EXAMPLES
    Here's how to use zombies and get_reject in your plugin:

     sub register {
        my ($self,$qp) = shift, shift;
        $self->{_args} = { @_ };
        $self->{_args}{reject} ||= 'zombie';
     };

     sub connect_handler {
         my ($self, $transaction) = @_;
         ... do a bunch of stuff ...
         return DECLINED if is_okay();
         return $self->get_reject( $message );
     };

AUTHOR
     2012 - Matt Simerson - msimer...@cpan.org



`````````````````````````````````````````````````````````````````````````
  Matt Simerson                   http://matt.simerson.net/
  Systems Engineer            http://www.tnpi.net/

  Mail::Toaster  - http://mail-toaster.org/
  NicTool          - http://www.nictool.com/
`````````````````````````````````````````````````````````````````````````

Reply via email to