Author: vetinari
Date: Fri Jan 25 11:35:49 2008
New Revision: 838

Added:
   contrib/vetinari/pipeline_sync

Log:
pipeline_sync - advanced check_earlytalker plugin
 * checks at connect, data, vrfy and noop
 * different wait times for each hook


Added: contrib/vetinari/pipeline_sync
==============================================================================
--- (empty file)
+++ contrib/vetinari/pipeline_sync      Fri Jan 25 11:35:49 2008
@@ -0,0 +1,216 @@
+
+=head1 NAME
+
+pipeline_sync - advanced version of the check_earlytalker plugin
+
+=head1 DESCRIPTION
+
+This plugin checks if the remote host honours RFC 1854 (SMTP Service Extension
+for Command Pipelining), which we announce:
+
+ ``The EHLO, DATA, VRFY, EXPN, TURN, QUIT, and NOOP commands can 
+   only appear as the last command in a group since their success 
+   or failure produces a change of state which the client SMTP 
+   must accommodate. (NOOP is included in this group so it can be 
+   used as a synchronization point.)''
+
+The B<pipeline_sync> plugin may be configure to check at EHLO/HELO, DATA,
+VRFY and NOOP. QUIT would be to late anyway and the other commands are not
+supported by qpsmtpd. Any data sent by the client before the configured 
+timeout is a no-go and the hook returns with the specified action.
+
+=head1 CONFIGURATION
+
+Arguments are key/value pairs. Setting the TIMEOUT values below to C<0> 
+disables the checks for the given hook. Known arguments are:
+
+=over 4
+
+=item connect TIMEOUT
+
+Wait TIMEOUT seconds at connect before sending out the greeting banner, default
+timeout is C<1>.
+
+=item data TIMEOUT
+
+Wait TIMEOUT seconds before sending the C<354> message to the client, default
+timeout is C<1>.
+
+
+=item noop TIMEOUT
+
+Wait TIMEOUT seconds before returning C<250 OK> to the client, this check is
+disabled by default.
+
+=item vrfy TIMEOUT
+
+Wait TIMEOUT seconds before the other hook_vrfy() do something, this check is
+disabled by default.
+
+=item action DENY(SOFT)?(_DISCONNECT)?
+
+Sent (a temporary) error to the client and optionally disconnect, see 
+F<docs/plugins.pod> and F<Qpsmtpd::Constants> for more info about the
+DENY, DENYSOFT, DENY_DISCONNECT and DENYSOFT_DISCONNECT constants.
+
+Default action is C<DENY>.
+
+=item defer BOOLEAN
+
+If set to a true value, clients talking before the greeting banner will be 
+punished after sending C<MAIL FROM> instead of immediately. Default is NOT
+to defer pubishing.
+
+=back
+
+=head1 NOTES
+
+C<hook_vrfy()> does currently not support DENYSOFT* and DENY_DISCONNECT.
+
+C<hook_noop()> does currently not support DENYSOFT*.
+
+Drop me a note if you want to use one of the above.
+
+=head1 AUTHOR
+
+Based on the check_earlytalker plugin from the qpsmtpd distribution.
+
+(c) 2008, Hanno Hecker <[EMAIL PROTECTED]>
+
+This software is free software and may be distributed under the same
+terms as qpsmtpd itself.
+
+=cut
+
+use IO::Select;
+
+use warnings;
+use strict;
+
+sub register {
+    my ($self, $qp, %args) = @_;
+
+    $self->{_is_apache} = 0;
+    $self->{_action}    = "DENY";
+    $self->{_defer}     = 0;
+    my %checks = ( # default values... timeout in seconds
+               'connect' => 1,
+               'data'    => 1,
+               'vrfy'    => 0,
+               'noop'    => 0,
+           );
+
+    foreach my $key (keys %args) {
+        if (exists $checks{$key}) {
+            next unless $args{$key} =~ /^\d+$/;
+            $checks{$key} = $args{$key};
+        }
+        elsif ($key eq "action") {
+            next unless $args{"action"} =~ /^DENY(SOFT)?(_DISCONNECT)?$/i;
+            $self->{_action} = Qpsmtpd::Constants::return_code(uc 
$args{"action"});
+        }
+        elsif ($key eq "defer-reject") {
+            $self->{_defer} = $args{"defer-reject"} ? 1 : 0;
+        }
+    }
+    $self->{_checks} = \%checks;
+
+    if ($qp->{conn} && $qp->{conn}->isa('Apache2::Connection')) {
+        require APR::Const;
+        APR::Const->import(qw(POLLIN SUCCESS));
+        $self->{_is_apache} = 1;
+    }
+    1;
+}
+
+sub sync_check {
+    my $self = shift;
+
+    my $hook = $self->hook_name();
+    my $timeout = $self->{_checks}->{$hook} || 0;
+    ## print STDERR "hook = $hook(), timeout = $timeout\n";
+    return (DECLINED) unless $timeout;
+
+    return DECLINED
+      if ($self->qp->connection->notes('whitelistclient'));
+
+    my $rc = 0;
+    if ($self->{_is_apache}) {
+        $rc = $self->wait_apache($timeout);
+    }
+    else {
+        $rc = $self->wait_qpsmtpd($timeout);
+    }
+    return (DECLINED, "remote host said nothing spontaneous at hook_$hook(), 
proceeding")
+      unless ($rc);
+
+    my $ip = $self->qp->connection->remote_ip;
+    
+    $_ = $hook;
+    /^connect$/ 
+      and do {
+        $self->log(LOGNOTICE, "remote host [$ip] started talking before we 
said hello");
+        if ($self->{_defer}) {
+            $self->qp->connection->notes('earlytalker', 1);
+            return (DECLINED);
+        }
+        return($self->{_action}, "Connecting host started transmitting before 
SMTP greeting");
+    };
+
+    /^data$/ 
+      and $self->log(LOGNOTICE, "remote host [$ip] started with message before 
we allowed to"),
+        return($self->{_action}, "I wasn't ready to receive your message");
+
+    /^noop$/ 
+      and $self->log(LOGNOTICE, "remote host [$ip] didn't wait after NOOP"),
+        return($self->{_action}, "NOOP took longer than you expected?");
+
+    /^vrfy$/ 
+      and $self->log(LOGNOTICE, "remote host [$ip] didn't wait for address 
verification"),
+        return($self->{_action}, "Not enough time to wait for verification of 
recipient?");
+
+    return (DECLINED); # how did we get here?
+}
+
+*hook_connect = 
+    *hook_data = 
+    *hook_noop = 
+    *hook_vrfy =
+        \&sync_check;
+
+sub hook_mail {
+    my $self = shift;
+    return (DECLINED)
+      unless ($self->{_defer} and $self->qp->connection->notes('earlytalker'));
+
+    return($self->{_action}, 
+        "Connecting host started transmitting before SMTP greeting");
+}
+
+sub wait_apache {
+    my ($self, $timeout) = @_;
+
+    $timeout *= 1_000_000;
+
+    my $c = $self->qp->{conn};
+    my $socket = $c->client_socket;
+
+    my $rc = $socket->poll($c->pool, $timeout, APR::Const::POLLIN());
+    return 1 if ($rc == APR::Const::SUCCESS());
+        
+    return 0;
+}
+
+sub wait_qpsmtpd {
+    my ($self, $timeout) = @_;
+    my $in = new IO::Select;
+
+    $in->add(\*STDIN) || return 0;
+    return 1 if $in->can_read($timeout);
+    
+    return 0;
+}
+
+1;
+
+# vim: ts=4 sw=4 expandtab syn=perl

Reply via email to