Continuation support
git-svn-id: https://svn.perl.org/qpsmtpd/branches/high_perf@436 958fd67b-6ff1-0310-b445-bb7760255be9
This commit is contained in:
parent
6ed494275b
commit
a4517bdfa4
@ -208,38 +208,20 @@ sub _load_plugins {
|
|||||||
|
|
||||||
sub run_hooks {
|
sub run_hooks {
|
||||||
my ($self, $hook) = (shift, shift);
|
my ($self, $hook) = (shift, shift);
|
||||||
|
if ($self->{_continuation}) {
|
||||||
|
die "Continuations in progress from previous hook (this is the $hook hook)";
|
||||||
|
}
|
||||||
my $hooks = $self->{hooks};
|
my $hooks = $self->{hooks};
|
||||||
if ($hooks->{$hook}) {
|
if ($hooks->{$hook}) {
|
||||||
my @r;
|
my @r;
|
||||||
for my $code (@{$hooks->{$hook}}) {
|
my @local_hooks = @{$hooks->{$hook}};
|
||||||
$self->log(LOGINFO, "running plugin ($hook):", $code->{name});
|
while (@local_hooks) {
|
||||||
eval { (@r) = $code->{code}->($self, $self->transaction, @_); };
|
my $code = shift @local_hooks;
|
||||||
$@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and next;
|
@r = $self->run_hook($hook, $code, @_);
|
||||||
|
next unless @r;
|
||||||
!defined $r[0]
|
if ($r[0] == CONTINUATION) {
|
||||||
and $self->log(LOGERROR, "plugin ".$code->{name}
|
$self->{_continuation} = [$hook, [@_], @local_hooks];
|
||||||
."running the $hook hook returned undef!")
|
|
||||||
and next;
|
|
||||||
|
|
||||||
if ($self->transaction) {
|
|
||||||
my $tnotes = $self->transaction->notes( $code->{name} );
|
|
||||||
$tnotes->{"hook_$hook"}->{'return'} = $r[0]
|
|
||||||
if (!defined $tnotes || ref $tnotes eq "HASH");
|
|
||||||
} else {
|
|
||||||
my $cnotes = $self->connection->notes( $code->{name} );
|
|
||||||
$cnotes->{"hook_$hook"}->{'return'} = $r[0]
|
|
||||||
if (!defined $cnotes || ref $cnotes eq "HASH");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# should we have a hook for "OK" too?
|
|
||||||
if ($r[0] == DENY or $r[0] == DENYSOFT or
|
|
||||||
$r[0] == DENY_DISCONNECT or $r[0] == DENYSOFT_DISCONNECT)
|
|
||||||
{
|
|
||||||
$r[1] = "" if not defined $r[1];
|
|
||||||
$self->log(LOGDEBUG, "Plugin $code->{name}, hook $hook returned $r[0], $r[1]");
|
|
||||||
$self->run_hooks("deny", $code->{name}, $r[0], $r[1]) unless ($hook eq "deny");
|
|
||||||
}
|
|
||||||
|
|
||||||
last unless $r[0] == DECLINED;
|
last unless $r[0] == DECLINED;
|
||||||
}
|
}
|
||||||
$r[0] = DECLINED if not defined $r[0];
|
$r[0] = DECLINED if not defined $r[0];
|
||||||
@ -248,6 +230,65 @@ sub run_hooks {
|
|||||||
return (0, '');
|
return (0, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub finish_continuation {
|
||||||
|
my ($self) = @_;
|
||||||
|
die "No continuation in progress" unless $self->{_continuation};
|
||||||
|
my $todo = $self->{_continuation};
|
||||||
|
$self->{_continuation} = undef;
|
||||||
|
my $hook = shift @$todo || die "No hook in the continuation";
|
||||||
|
my $args = shift @$todo || die "No hook args in the continuation";
|
||||||
|
my @r;
|
||||||
|
while (@$todo) {
|
||||||
|
my $code = shift @$todo;
|
||||||
|
@r = $self->run_hook($hook, $code, @$args);
|
||||||
|
if ($r[0] == CONTINUATION) {
|
||||||
|
$self->{_continuation} = [$hook, $args, @$todo];
|
||||||
|
return @r;
|
||||||
|
}
|
||||||
|
last unless $r[0] == DECLINED;
|
||||||
|
}
|
||||||
|
$r[0] = DECLINED if not defined $r[0];
|
||||||
|
my $responder = $hook . "_respond";
|
||||||
|
if (my $meth = $self->can($responder)) {
|
||||||
|
return $meth->($self, @r, @$args);
|
||||||
|
}
|
||||||
|
die "No ${hook}_respond method";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub run_hook {
|
||||||
|
my ($self, $hook, $code, @args) = @_;
|
||||||
|
my @r;
|
||||||
|
$self->log(LOGINFO, "running plugin ($hook):", $code->{name});
|
||||||
|
eval { (@r) = $code->{code}->($self, $self->transaction, @args); };
|
||||||
|
$@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and return;
|
||||||
|
|
||||||
|
!defined $r[0]
|
||||||
|
and $self->log(LOGERROR, "plugin ".$code->{name}
|
||||||
|
."running the $hook hook returned undef!")
|
||||||
|
and return;
|
||||||
|
|
||||||
|
if ($self->transaction) {
|
||||||
|
my $tnotes = $self->transaction->notes( $code->{name} );
|
||||||
|
$tnotes->{"hook_$hook"}->{'return'} = $r[0]
|
||||||
|
if (!defined $tnotes || ref $tnotes eq "HASH");
|
||||||
|
} else {
|
||||||
|
my $cnotes = $self->connection->notes( $code->{name} );
|
||||||
|
$cnotes->{"hook_$hook"}->{'return'} = $r[0]
|
||||||
|
if (!defined $cnotes || ref $cnotes eq "HASH");
|
||||||
|
}
|
||||||
|
|
||||||
|
# should we have a hook for "OK" too?
|
||||||
|
if ($r[0] == DENY or $r[0] == DENYSOFT or
|
||||||
|
$r[0] == DENY_DISCONNECT or $r[0] == DENYSOFT_DISCONNECT)
|
||||||
|
{
|
||||||
|
$r[1] = "" if not defined $r[1];
|
||||||
|
$self->log(LOGDEBUG, "Plugin $code->{name}, hook $hook returned $r[0], $r[1]");
|
||||||
|
$self->run_hooks("deny", $code->{name}, $r[0], $r[1]) unless ($hook eq "deny");
|
||||||
|
}
|
||||||
|
|
||||||
|
return @r;
|
||||||
|
}
|
||||||
|
|
||||||
sub _register_hook {
|
sub _register_hook {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($hook, $code, $unshift) = @_;
|
my ($hook, $code, $unshift) = @_;
|
||||||
|
@ -3,7 +3,7 @@ use strict;
|
|||||||
require Exporter;
|
require Exporter;
|
||||||
|
|
||||||
my (@common) = qw(OK DECLINED DONE DENY DENYSOFT DENYHARD
|
my (@common) = qw(OK DECLINED DONE DENY DENYSOFT DENYHARD
|
||||||
DENY_DISCONNECT DENYSOFT_DISCONNECT
|
DENY_DISCONNECT DENYSOFT_DISCONNECT CONTINUATION
|
||||||
);
|
);
|
||||||
my (@loglevels) = qw(LOGDEBUG LOGINFO LOGNOTICE LOGWARN LOGERROR LOGCRIT LOGALERT LOGEMERG LOGRADAR);
|
my (@loglevels) = qw(LOGDEBUG LOGINFO LOGNOTICE LOGWARN LOGERROR LOGCRIT LOGALERT LOGEMERG LOGRADAR);
|
||||||
|
|
||||||
@ -11,14 +11,15 @@ use vars qw($VERSION @ISA @EXPORT);
|
|||||||
@ISA = qw(Exporter);
|
@ISA = qw(Exporter);
|
||||||
@EXPORT = (@common, @loglevels);
|
@EXPORT = (@common, @loglevels);
|
||||||
|
|
||||||
use constant OK => 900;
|
use constant OK => 900;
|
||||||
use constant DENY => 901; # 550
|
use constant DENY => 901; # 550
|
||||||
use constant DENYSOFT => 902; # 450
|
use constant DENYSOFT => 902; # 450
|
||||||
use constant DENYHARD => 903; # 550 + disconnect (deprecated in 0.29)
|
use constant DENYHARD => 903; # 550 + disconnect (deprecated in 0.29)
|
||||||
use constant DENY_DISCONNECT => 903; # 550 + disconnect
|
use constant DENY_DISCONNECT => 903; # 550 + disconnect
|
||||||
use constant DENYSOFT_DISCONNECT => 904; # 450 + disconnect
|
use constant DENYSOFT_DISCONNECT => 904; # 450 + disconnect
|
||||||
use constant DECLINED => 909;
|
use constant DECLINED => 909;
|
||||||
use constant DONE => 910;
|
use constant DONE => 910;
|
||||||
|
use constant CONTINUATION => 911;
|
||||||
|
|
||||||
|
|
||||||
# log levels
|
# log levels
|
||||||
|
@ -21,6 +21,7 @@ use fields qw(
|
|||||||
_transaction
|
_transaction
|
||||||
_test_mode
|
_test_mode
|
||||||
_extras
|
_extras
|
||||||
|
_continuation
|
||||||
);
|
);
|
||||||
use Qpsmtpd::Constants;
|
use Qpsmtpd::Constants;
|
||||||
use Qpsmtpd::Auth;
|
use Qpsmtpd::Auth;
|
||||||
@ -95,6 +96,13 @@ sub fault {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub log {
|
||||||
|
my ($self, $trace, @log) = @_;
|
||||||
|
my $fd = $self->{fd};
|
||||||
|
$fd ||= '?';
|
||||||
|
$self->SUPER::log($trace, "fd:$fd", @log);
|
||||||
|
}
|
||||||
|
|
||||||
sub process_line {
|
sub process_line {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my $line = shift || return;
|
my $line = shift || return;
|
||||||
@ -164,17 +172,8 @@ sub process_cmd {
|
|||||||
else {
|
else {
|
||||||
# No such method - i.e. unrecognized command
|
# No such method - i.e. unrecognized command
|
||||||
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $cmd);
|
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $cmd);
|
||||||
if ($rc == DENY) {
|
return $self->unrecognized_command_respond unless $rc == CONTINUATION;
|
||||||
$self->respond(521, $msg);
|
return 1;
|
||||||
$self->disconnect;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
elsif ($rc == DONE) {
|
|
||||||
return; # TODO - this isn't right.
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return $self->respond(500, "Unrecognized command");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,29 +200,20 @@ sub start_conversation {
|
|||||||
);
|
);
|
||||||
|
|
||||||
my ($rc, $msg) = $self->run_hooks("connect");
|
my ($rc, $msg) = $self->run_hooks("connect");
|
||||||
if ($rc == DENY) {
|
return $self->connect_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
$self->respond(550, ($msg || 'Connection from you denied, bye bye.'));
|
return DONE;
|
||||||
return $rc;
|
|
||||||
}
|
|
||||||
elsif ($rc == DENYSOFT) {
|
|
||||||
$self->respond(450, ($msg || 'Connection from you temporarily denied, bye bye.'));
|
|
||||||
return $rc;
|
|
||||||
}
|
|
||||||
elsif ($rc == DONE) {
|
|
||||||
$self->respond(220, $msg);
|
|
||||||
return $rc;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$self->respond(220, $self->config('me') ." ESMTP qpsmtpd "
|
|
||||||
. $self->version ." ready; send us your mail, but not your spam.");
|
|
||||||
return DONE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub data {
|
sub data {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
|
|
||||||
my ($rc, $msg) = $self->run_hooks("data");
|
my ($rc, $msg) = $self->run_hooks("data");
|
||||||
|
return $self->data_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub data_respond {
|
||||||
|
my ($self, $rc, $msg) = @_;
|
||||||
if ($rc == DONE) {
|
if ($rc == DONE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -350,22 +340,8 @@ sub end_of_data {
|
|||||||
return $self->respond(552, "Message too big!") if $self->{max_size} and $self->{data_size} > $self->{max_size};
|
return $self->respond(552, "Message too big!") if $self->{max_size} and $self->{data_size} > $self->{max_size};
|
||||||
|
|
||||||
my ($rc, $msg) = $self->run_hooks("data_post");
|
my ($rc, $msg) = $self->run_hooks("data_post");
|
||||||
if ($rc == DONE) {
|
return $self->data_post_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
return;
|
return 1;
|
||||||
}
|
|
||||||
elsif ($rc == DENY) {
|
|
||||||
$self->respond(552, $msg || "Message denied");
|
|
||||||
}
|
|
||||||
elsif ($rc == DENYSOFT) {
|
|
||||||
$self->respond(452, $msg || "Message denied temporarily");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$self->queue($self->transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
# DATA is always the end of a "transaction"
|
|
||||||
$self->reset_transaction;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
@ -54,18 +54,9 @@ sub dispatch {
|
|||||||
# if $state{dnsbl_blocked} and ($cmd eq "rcpt");
|
# if $state{dnsbl_blocked} and ($cmd eq "rcpt");
|
||||||
|
|
||||||
if ($cmd !~ /^(\w{1,12})$/ or !exists $self->{_commands}->{$1}) {
|
if ($cmd !~ /^(\w{1,12})$/ or !exists $self->{_commands}->{$1}) {
|
||||||
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $cmd);
|
my ($rc, $msg) = $self->run_hooks("unrecognized_command", $cmd, @_);
|
||||||
if ($rc == DENY) {
|
return $self->unrecognized_command_respond($rc, $msg, @_) unless $rc == CONTINUATION;
|
||||||
$self->respond(521, $msg);
|
return 1;
|
||||||
$self->disconnect;
|
|
||||||
}
|
|
||||||
elsif ($rc == DONE) {
|
|
||||||
1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$self->respond(500, "Unrecognized command");
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
$cmd = $1;
|
$cmd = $1;
|
||||||
|
|
||||||
@ -79,6 +70,17 @@ sub dispatch {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub unrecognized_command_respond {
|
||||||
|
my ($self, $rc, $msg) = @_;
|
||||||
|
if ($rc == DENY) {
|
||||||
|
$self->respond(521, $msg);
|
||||||
|
$self->disconnect;
|
||||||
|
}
|
||||||
|
elsif ($rc != DONE) {
|
||||||
|
$self->respond(500, "Unrecognized command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub fault {
|
sub fault {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($msg) = shift || "program fault - command not performed";
|
my ($msg) = shift || "program fault - command not performed";
|
||||||
@ -92,6 +94,12 @@ sub start_conversation {
|
|||||||
# this should maybe be called something else than "connect", see
|
# this should maybe be called something else than "connect", see
|
||||||
# lib/Qpsmtpd/TcpServer.pm for more confusion.
|
# lib/Qpsmtpd/TcpServer.pm for more confusion.
|
||||||
my ($rc, $msg) = $self->run_hooks("connect");
|
my ($rc, $msg) = $self->run_hooks("connect");
|
||||||
|
return $self->connect_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub connect_respond {
|
||||||
|
my ($self, $rc, $msg) = @_;
|
||||||
if ($rc == DENY) {
|
if ($rc == DENY) {
|
||||||
$self->respond(550, ($msg || 'Connection from you denied, bye bye.'));
|
$self->respond(550, ($msg || 'Connection from you denied, bye bye.'));
|
||||||
return $rc;
|
return $rc;
|
||||||
@ -118,17 +126,25 @@ sub helo {
|
|||||||
return $self->respond (503, "but you already said HELO ...") if $conn->hello;
|
return $self->respond (503, "but you already said HELO ...") if $conn->hello;
|
||||||
|
|
||||||
my ($rc, $msg) = $self->run_hooks("helo", $hello_host, @stuff);
|
my ($rc, $msg) = $self->run_hooks("helo", $hello_host, @stuff);
|
||||||
if ($rc == DONE) {
|
return $self->helo_respond($rc, $msg, $hello_host, @stuff) unless $rc == CONTINUATION;
|
||||||
# do nothing
|
return 1;
|
||||||
} elsif ($rc == DENY) {
|
}
|
||||||
|
|
||||||
|
sub helo_respond {
|
||||||
|
my ($self, $rc, $msg, $hello_host) = @_;
|
||||||
|
if ($rc == DENY) {
|
||||||
$self->respond(550, $msg);
|
$self->respond(550, $msg);
|
||||||
} elsif ($rc == DENYSOFT) {
|
}
|
||||||
|
elsif ($rc == DENYSOFT) {
|
||||||
$self->respond(450, $msg);
|
$self->respond(450, $msg);
|
||||||
} else {
|
}
|
||||||
|
elsif ($rc != DONE) {
|
||||||
|
my $conn = $self->connection;
|
||||||
$conn->hello("helo");
|
$conn->hello("helo");
|
||||||
$conn->hello_host($hello_host);
|
$conn->hello_host($hello_host);
|
||||||
$self->transaction;
|
$self->transaction;
|
||||||
$self->respond(250, $self->config('me') ." Hi " . $conn->remote_info . " [" . $conn->remote_ip ."]; I am so happy to meet you.");
|
$self->respond(250, $self->config('me') ." Hi " . $conn->remote_info .
|
||||||
|
" [" . $conn->remote_ip ."]; I am so happy to meet you.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,13 +156,20 @@ sub ehlo {
|
|||||||
return $self->respond (503, "but you already said HELO ...") if $conn->hello;
|
return $self->respond (503, "but you already said HELO ...") if $conn->hello;
|
||||||
|
|
||||||
my ($rc, $msg) = $self->run_hooks("ehlo", $hello_host, @stuff);
|
my ($rc, $msg) = $self->run_hooks("ehlo", $hello_host, @stuff);
|
||||||
if ($rc == DONE) {
|
return $self->ehlo_respond($rc, $msg, $hello_host, @stuff) unless $rc == CONTINUATION;
|
||||||
# do nothing
|
return 1;
|
||||||
} elsif ($rc == DENY) {
|
}
|
||||||
|
|
||||||
|
sub ehlo_respond {
|
||||||
|
my ($self, $rc, $msg, $hello_host) = @_;
|
||||||
|
if ($rc == DENY) {
|
||||||
$self->respond(550, $msg);
|
$self->respond(550, $msg);
|
||||||
} elsif ($rc == DENYSOFT) {
|
}
|
||||||
|
elsif ($rc == DENYSOFT) {
|
||||||
$self->respond(450, $msg);
|
$self->respond(450, $msg);
|
||||||
} else {
|
}
|
||||||
|
elsif ($rc != DONE) {
|
||||||
|
my $conn = $self->connection;
|
||||||
$conn->hello("ehlo");
|
$conn->hello("ehlo");
|
||||||
$conn->hello_host($hello_host);
|
$conn->hello_host($hello_host);
|
||||||
$self->transaction;
|
$self->transaction;
|
||||||
@ -211,57 +234,62 @@ sub mail {
|
|||||||
unless ($self->connection->hello) {
|
unless ($self->connection->hello) {
|
||||||
return $self->respond(503, "please say hello first ...");
|
return $self->respond(503, "please say hello first ...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $from_parameter = join " ", @_;
|
||||||
|
$self->log(LOGINFO, "full from_parameter: $from_parameter");
|
||||||
|
|
||||||
|
my ($from) = ($from_parameter =~ m/^from:\s*(<[^>]*>)/i)[0];
|
||||||
|
|
||||||
|
# support addresses without <> ... maybe we shouldn't?
|
||||||
|
($from) = "<" . ($from_parameter =~ m/^from:\s*(\S+)/i)[0] . ">"
|
||||||
|
unless $from;
|
||||||
|
|
||||||
|
$self->log(LOGWARN, "from email address : [$from]");
|
||||||
|
|
||||||
|
if ($from eq "<>" or $from =~ m/\[undefined\]/ or $from eq "<#@[]>") {
|
||||||
|
$from = Qpsmtpd::Address->new("<>");
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
my $from_parameter = join " ", @_;
|
$from = (Qpsmtpd::Address->parse($from))[0];
|
||||||
$self->log(LOGINFO, "full from_parameter: $from_parameter");
|
}
|
||||||
|
return $self->respond(501, "could not parse your mail from command") unless $from;
|
||||||
|
|
||||||
my ($from) = ($from_parameter =~ m/^from:\s*(<[^>]*>)/i)[0];
|
my ($rc, $msg) = $self->run_hooks("mail", $from);
|
||||||
|
return $self->mail_respond($rc, $msg, $from) unless $rc == CONTINUATION;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
# support addresses without <> ... maybe we shouldn't?
|
sub mail_respond {
|
||||||
($from) = "<" . ($from_parameter =~ m/^from:\s*(\S+)/i)[0] . ">"
|
my ($self, $rc, $msg, $from) = @_;
|
||||||
unless $from;
|
if ($rc == DONE) {
|
||||||
|
return 1;
|
||||||
$self->log(LOGWARN, "from email address : [$from]");
|
}
|
||||||
|
elsif ($rc == DENY) {
|
||||||
if ($from eq "<>" or $from =~ m/\[undefined\]/ or $from eq "<#@[]>") {
|
$msg ||= $from->format . ', denied';
|
||||||
$from = Qpsmtpd::Address->new("<>");
|
$self->log(LOGINFO, "deny mail from " . $from->format . " ($msg)");
|
||||||
}
|
$self->respond(550, $msg);
|
||||||
else {
|
}
|
||||||
$from = (Qpsmtpd::Address->parse($from))[0];
|
elsif ($rc == DENYSOFT) {
|
||||||
}
|
$msg ||= $from->format . ', temporarily denied';
|
||||||
return $self->respond(501, "could not parse your mail from command") unless $from;
|
$self->log(LOGINFO, "denysoft mail from " . $from->format . " ($msg)");
|
||||||
|
$self->respond(450, $msg);
|
||||||
my ($rc, $msg) = $self->run_hooks("mail", $from);
|
}
|
||||||
if ($rc == DONE) {
|
elsif ($rc == DENY_DISCONNECT) {
|
||||||
return 1;
|
$msg ||= $from->format . ', denied';
|
||||||
}
|
$self->log(LOGINFO, "deny mail from " . $from->format . " ($msg)");
|
||||||
elsif ($rc == DENY) {
|
$self->respond(550, $msg);
|
||||||
$msg ||= $from->format . ', denied';
|
$self->disconnect;
|
||||||
$self->log(LOGINFO, "deny mail from " . $from->format . " ($msg)");
|
}
|
||||||
$self->respond(550, $msg);
|
elsif ($rc == DENYSOFT_DISCONNECT) {
|
||||||
}
|
$msg ||= $from->format . ', temporarily denied';
|
||||||
elsif ($rc == DENYSOFT) {
|
$self->log(LOGINFO, "denysoft mail from " . $from->format . " ($msg)");
|
||||||
$msg ||= $from->format . ', temporarily denied';
|
$self->respond(450, $msg);
|
||||||
$self->log(LOGINFO, "denysoft mail from " . $from->format . " ($msg)");
|
$self->disconnect;
|
||||||
$self->respond(450, $msg);
|
}
|
||||||
}
|
else { # includes OK
|
||||||
elsif ($rc == DENY_DISCONNECT) {
|
$self->log(LOGINFO, "getting mail from ".$from->format);
|
||||||
$msg ||= $from->format . ', denied';
|
$self->respond(250, $from->format . ", sender OK - how exciting to get mail from you!");
|
||||||
$self->log(LOGINFO, "deny mail from " . $from->format . " ($msg)");
|
$self->transaction->sender($from);
|
||||||
$self->respond(550, $msg);
|
|
||||||
$self->disconnect;
|
|
||||||
}
|
|
||||||
elsif ($rc == DENYSOFT_DISCONNECT) {
|
|
||||||
$msg ||= $from->format . ', temporarily denied';
|
|
||||||
$self->log(LOGINFO, "denysoft mail from " . $from->format . " ($msg)");
|
|
||||||
$self->respond(450, $msg);
|
|
||||||
$self->disconnect;
|
|
||||||
}
|
|
||||||
else { # includes OK
|
|
||||||
$self->log(LOGINFO, "getting mail from ".$from->format);
|
|
||||||
$self->respond(250, $from->format . ", sender OK - how exciting to get mail from you!");
|
|
||||||
$self->transaction->sender($from);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,6 +306,12 @@ sub rcpt {
|
|||||||
return $self->respond(501, "could not parse recipient") unless $rcpt;
|
return $self->respond(501, "could not parse recipient") unless $rcpt;
|
||||||
|
|
||||||
my ($rc, $msg) = $self->run_hooks("rcpt", $rcpt);
|
my ($rc, $msg) = $self->run_hooks("rcpt", $rcpt);
|
||||||
|
return $self->rcpt_respond($rc, $msg, $rcpt) unless $rc == CONTINUATION;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rcpt_respond {
|
||||||
|
my ($self, $rc, $msg, $rcpt) = @_;
|
||||||
if ($rc == DONE) {
|
if ($rc == DONE) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -312,7 +346,6 @@ sub rcpt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
sub help {
|
sub help {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
$self->respond(214,
|
$self->respond(214,
|
||||||
@ -334,6 +367,12 @@ sub vrfy {
|
|||||||
# I also don't think it provides all the proper result codes.
|
# I also don't think it provides all the proper result codes.
|
||||||
|
|
||||||
my ($rc, $msg) = $self->run_hooks("vrfy");
|
my ($rc, $msg) = $self->run_hooks("vrfy");
|
||||||
|
return $self->vrfy_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub vrfy_respond {
|
||||||
|
my ($self, $rc, $msg) = @_;
|
||||||
if ($rc == DONE) {
|
if ($rc == DONE) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -361,6 +400,12 @@ sub rset {
|
|||||||
sub quit {
|
sub quit {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($rc, $msg) = $self->run_hooks("quit");
|
my ($rc, $msg) = $self->run_hooks("quit");
|
||||||
|
return $self->quit_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub quit_respond {
|
||||||
|
my ($self, $rc, $msg) = @_;
|
||||||
if ($rc != DONE) {
|
if ($rc != DONE) {
|
||||||
$self->respond(221, $self->config('me') . " closing connection. Have a wonderful day.");
|
$self->respond(221, $self->config('me') . " closing connection. Have a wonderful day.");
|
||||||
}
|
}
|
||||||
@ -373,9 +418,17 @@ sub disconnect {
|
|||||||
$self->reset_transaction;
|
$self->reset_transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub disconnect_respond { }
|
||||||
|
|
||||||
sub data {
|
sub data {
|
||||||
my $self = shift;
|
my $self = shift;
|
||||||
my ($rc, $msg) = $self->run_hooks("data");
|
my ($rc, $msg) = $self->run_hooks("data");
|
||||||
|
return $self->data_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub data_respond {
|
||||||
|
my ($self, $rc, $msg) = @_;
|
||||||
if ($rc == DONE) {
|
if ($rc == DONE) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -493,6 +546,11 @@ sub data {
|
|||||||
$self->respond(552, "Message too big!"),return 1 if $max_size and $size > $max_size;
|
$self->respond(552, "Message too big!"),return 1 if $max_size and $size > $max_size;
|
||||||
|
|
||||||
($rc, $msg) = $self->run_hooks("data_post");
|
($rc, $msg) = $self->run_hooks("data_post");
|
||||||
|
return $self->data_post_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub data_post_respond {
|
||||||
|
my ($self, $rc, $msg) = @_;
|
||||||
if ($rc == DONE) {
|
if ($rc == DONE) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -508,7 +566,6 @@ sub data {
|
|||||||
|
|
||||||
# DATA is always the end of a "transaction"
|
# DATA is always the end of a "transaction"
|
||||||
return $self->reset_transaction;
|
return $self->reset_transaction;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub getline {
|
sub getline {
|
||||||
@ -524,6 +581,12 @@ sub queue {
|
|||||||
my ($self, $transaction) = @_;
|
my ($self, $transaction) = @_;
|
||||||
|
|
||||||
my ($rc, $msg) = $self->run_hooks("queue");
|
my ($rc, $msg) = $self->run_hooks("queue");
|
||||||
|
return $self->queue_respond($rc, $msg) unless $rc == CONTINUATION;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub queue_respond {
|
||||||
|
my ($self, $rc, $msg) = @_;
|
||||||
if ($rc == DONE) {
|
if ($rc == DONE) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -539,8 +602,6 @@ sub queue {
|
|||||||
else {
|
else {
|
||||||
$self->respond(451, $msg || "Queuing declined or disabled; try again later" );
|
$self->respond(451, $msg || "Queuing declined or disabled; try again later" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use Danga::DNS;
|
|||||||
sub register {
|
sub register {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
$self->register_hook("connect", "connect_handler");
|
$self->register_hook("connect", "connect_handler");
|
||||||
$self->register_hook("rcpt", "rcpt_handler");
|
$self->register_hook("connect", "pickup_handler");
|
||||||
}
|
}
|
||||||
|
|
||||||
sub connect_handler {
|
sub connect_handler {
|
||||||
@ -34,12 +34,14 @@ sub connect_handler {
|
|||||||
|
|
||||||
my $reversed_ip = join(".", reverse(split(/\./, $remote_ip)));
|
my $reversed_ip = join(".", reverse(split(/\./, $remote_ip)));
|
||||||
|
|
||||||
|
$self->transaction->notes('pending_dns_queries', scalar(keys(%dnsbl_zones)));
|
||||||
|
my $qp = $self->qp;
|
||||||
for my $dnsbl (keys %dnsbl_zones) {
|
for my $dnsbl (keys %dnsbl_zones) {
|
||||||
# fix to find A records, if the dnsbl_zones line has a second field 20/1/04 ++msp
|
# fix to find A records, if the dnsbl_zones line has a second field 20/1/04 ++msp
|
||||||
if (defined($dnsbl_zones{$dnsbl})) {
|
if (defined($dnsbl_zones{$dnsbl})) {
|
||||||
$self->log(LOGDEBUG, "Checking $reversed_ip.$dnsbl for A record in the background");
|
$self->log(LOGDEBUG, "Checking $reversed_ip.$dnsbl for A record in the background");
|
||||||
Danga::DNS->new(
|
Danga::DNS->new(
|
||||||
callback => sub { $self->process_a_result($dnsbl_zones{$dnsbl}, @_) },
|
callback => sub { process_a_result($qp, $dnsbl_zones{$dnsbl}, @_) },
|
||||||
host => "$reversed_ip.$dnsbl",
|
host => "$reversed_ip.$dnsbl",
|
||||||
type => 'A',
|
type => 'A',
|
||||||
client => $self->qp->input_sock,
|
client => $self->qp->input_sock,
|
||||||
@ -47,7 +49,7 @@ sub connect_handler {
|
|||||||
} else {
|
} else {
|
||||||
$self->log(LOGDEBUG, "Checking $reversed_ip.$dnsbl for TXT record in the background");
|
$self->log(LOGDEBUG, "Checking $reversed_ip.$dnsbl for TXT record in the background");
|
||||||
Danga::DNS->new(
|
Danga::DNS->new(
|
||||||
callback => sub { $self->process_txt_result(@_) },
|
callback => sub { process_txt_result($qp, @_) },
|
||||||
host => "$reversed_ip.$dnsbl",
|
host => "$reversed_ip.$dnsbl",
|
||||||
type => 'TXT',
|
type => 'TXT',
|
||||||
client => $self->qp->input_sock,
|
client => $self->qp->input_sock,
|
||||||
@ -55,40 +57,48 @@ sub connect_handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return DECLINED;
|
return CONTINUATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub process_a_result {
|
sub process_a_result {
|
||||||
my $self = shift;
|
my ($qp, $template, $result, $query) = @_;
|
||||||
my ($template, $result, $query) = @_;
|
|
||||||
|
my $pending = $qp->transaction->notes('pending_dns_queries');
|
||||||
|
$qp->transaction->notes('pending_dns_queries', --$pending);
|
||||||
|
|
||||||
warn("Result for A $query: $result\n");
|
warn("Result for A $query: $result\n");
|
||||||
if ($result !~ /^\d+\.\d+\.\d+\.\d+$/) {
|
if ($result !~ /^\d+\.\d+\.\d+\.\d+$/) {
|
||||||
# NXDOMAIN or ERROR possibly...
|
# NXDOMAIN or ERROR possibly...
|
||||||
|
$qp->finish_continuation unless $pending;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $ip = $self->connection->remote_ip;
|
my $conn = $qp->connection;
|
||||||
|
my $ip = $conn->remote_ip;
|
||||||
$template =~ s/%IP%/$ip/g;
|
$template =~ s/%IP%/$ip/g;
|
||||||
my $conn = $self->connection;
|
|
||||||
$conn->notes('dnsbl', $template) unless $conn->notes('dnsbl');
|
$conn->notes('dnsbl', $template) unless $conn->notes('dnsbl');
|
||||||
|
$qp->finish_continuation unless $pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub process_txt_result {
|
sub process_txt_result {
|
||||||
my $self = shift;
|
my ($qp, $result, $query) = @_;
|
||||||
my ($result, $query) = @_;
|
|
||||||
|
my $pending = $qp->transaction->notes('pending_dns_queries');
|
||||||
|
$qp->transaction->notes('pending_dns_queries', --$pending);
|
||||||
|
|
||||||
warn("Result for TXT $query: $result\n");
|
warn("Result for TXT $query: $result\n");
|
||||||
if ($result !~ /[a-z]/) {
|
if ($result !~ /[a-z]/) {
|
||||||
# NXDOMAIN or ERROR probably...
|
# NXDOMAIN or ERROR probably...
|
||||||
|
$qp->finish_continuation unless $pending;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $conn = $self->connection;
|
my $conn = $qp->connection;
|
||||||
$conn->notes('dnsbl', $result) unless $conn->notes('dnsbl');
|
$conn->notes('dnsbl', $result) unless $conn->notes('dnsbl');
|
||||||
|
$qp->finish_continuation unless $pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub rcpt_handler {
|
sub pickup_handler {
|
||||||
my ($self, $transaction, $rcpt) = @_;
|
my ($self, $transaction, $rcpt) = @_;
|
||||||
|
|
||||||
# RBLSMTPD being non-empty means it contains the failure message to return
|
# RBLSMTPD being non-empty means it contains the failure message to return
|
||||||
|
Loading…
Reference in New Issue
Block a user